mirror of
https://github.com/OPM/opm-simulators.git
synced 2026-11-01 17:03:06 -06:00
replacing it with a new PhaseUsageInfo and FluidSystem, both of which are using the same implementation of PhaseUsageInfo, and only one set of indices is used. Currently, it is the BlackOilDefaultFluidSystemIndices.
315 lines
11 KiB
C++
315 lines
11 KiB
C++
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
// vi: set et ts=4 sw=4 sts=4:
|
|
/*
|
|
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 2 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 <http://www.gnu.org/licenses/>.
|
|
|
|
Consult the COPYING file in the top-level source directory of this
|
|
module for the precise wording of the license and the list of
|
|
copyright holders.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <opm/simulators/flow/ActionHandler.hpp>
|
|
|
|
#include <opm/common/OpmLog/OpmLog.hpp>
|
|
#include <opm/common/utility/TimeService.hpp>
|
|
#include <opm/common/TimingMacros.hpp>
|
|
|
|
#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
|
|
|
|
#include <opm/input/eclipse/Schedule/Action/ActionContext.hpp>
|
|
#include <opm/input/eclipse/Schedule/Action/Actions.hpp>
|
|
#include <opm/input/eclipse/Schedule/Action/ActionX.hpp>
|
|
#include <opm/input/eclipse/Schedule/Action/SimulatorUpdate.hpp>
|
|
#include <opm/input/eclipse/Schedule/Action/State.hpp>
|
|
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
|
#include <opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp>
|
|
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
|
|
#include <opm/input/eclipse/Schedule/Well/WellMatcher.hpp>
|
|
|
|
#include <opm/material/fluidsystems/BlackOilDefaultFluidSystemIndices.hpp>
|
|
|
|
#include <opm/simulators/wells/BlackoilWellModelGeneric.hpp>
|
|
|
|
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
|
#include <opm/simulators/utils/ParallelSerialization.hpp>
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <ctime>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include <fmt/chrono.h>
|
|
#include <fmt/format.h>
|
|
#include <fmt/ranges.h>
|
|
|
|
namespace {
|
|
std::string formatActionDate(const Opm::TimeStampUTC& timePoint,
|
|
const int reportStep)
|
|
{
|
|
auto time_point = std::tm{};
|
|
|
|
time_point.tm_year = timePoint.year() - 1900;
|
|
time_point.tm_mon = timePoint.month() - 1;
|
|
time_point.tm_mday = timePoint.day();
|
|
|
|
time_point.tm_hour = timePoint.hour();
|
|
time_point.tm_min = timePoint.minutes();
|
|
time_point.tm_sec = timePoint.seconds();
|
|
|
|
return fmt::format("{:%d-%b-%Y %H:%M:%S} (report interval {} to {})",
|
|
time_point, reportStep, reportStep + 1);
|
|
}
|
|
|
|
void logActivePyAction(const std::string& actionName,
|
|
const std::string& timeString)
|
|
{
|
|
const auto message =
|
|
fmt::format("Action {} (Python) triggered at {}",
|
|
actionName, timeString);
|
|
|
|
Opm::OpmLog::info("ACTION_TRIGGERED", message);
|
|
}
|
|
|
|
template <typename WellNameRange>
|
|
void logActiveAction(const std::string& actionName,
|
|
const WellNameRange& matchingWells,
|
|
const std::string& timeString)
|
|
{
|
|
const auto wellString = matchingWells.empty()
|
|
? std::string{}
|
|
: fmt::format(" Well{}: {}",
|
|
matchingWells.size() != 1 ? "s" : "",
|
|
fmt::join(matchingWells, ", "));
|
|
|
|
const auto message =
|
|
fmt::format("Action {} triggered at {}.{}",
|
|
actionName, timeString, wellString);
|
|
|
|
Opm::OpmLog::info("ACTION_TRIGGERED", message);
|
|
}
|
|
|
|
void logInactivePyAction(const std::string& actionName,
|
|
const std::string& timeString)
|
|
{
|
|
const auto message =
|
|
fmt::format("Action {} (Python) NOT triggered at {}.",
|
|
actionName, timeString);
|
|
|
|
Opm::OpmLog::debug("NAMED_ACTION_NOT_TRIGGERED", message);
|
|
}
|
|
|
|
void logInactiveAction(const std::string& actionName,
|
|
const std::string& timeString)
|
|
{
|
|
const auto message =
|
|
fmt::format("Action {} NOT triggered at {}.",
|
|
actionName, timeString);
|
|
|
|
Opm::OpmLog::debug("NAMED_ACTION_NOT_TRIGGERED", message);
|
|
}
|
|
|
|
void logInactiveActions(const int numInactive,
|
|
const std::string& timeString)
|
|
{
|
|
const auto message =
|
|
fmt::format("{} action{} NOT triggered at {}.",
|
|
numInactive,
|
|
(numInactive != 1) ? "s" : "",
|
|
timeString);
|
|
|
|
Opm::OpmLog::debug("ACTION_NOT_TRIGGERED", message);
|
|
}
|
|
|
|
template <typename Scalar, class WellModel>
|
|
std::unordered_map<std::string, Scalar>
|
|
fetchWellPI(const WellModel& wellModel,
|
|
const std::vector<std::string>& wellpi_wells,
|
|
const Opm::Parallel::Communication comm)
|
|
{
|
|
auto wellpi = std::unordered_map<std::string, Scalar> {};
|
|
|
|
if (wellpi_wells.empty()) {
|
|
return wellpi;
|
|
}
|
|
|
|
auto wellPI = std::vector<Scalar>(wellpi_wells.size());
|
|
for (auto i = 0*wellpi_wells.size(); i < wellpi_wells.size(); ++i) {
|
|
if (wellModel.hasLocalWell(wellpi_wells[i])) {
|
|
wellPI[i] = wellModel.wellPI(wellpi_wells[i]);
|
|
}
|
|
}
|
|
|
|
comm.max(wellPI.data(), wellPI.size());
|
|
|
|
for (auto i = 0*wellpi_wells.size(); i < wellpi_wells.size(); ++i) {
|
|
wellpi.emplace(wellpi_wells[i], wellPI[i]);
|
|
}
|
|
|
|
return wellpi;
|
|
}
|
|
} // Anonymous namespace
|
|
|
|
namespace Opm {
|
|
|
|
template<typename Scalar, typename IndexTraits>
|
|
ActionHandler<Scalar, IndexTraits>::
|
|
ActionHandler(EclipseState& ecl_state,
|
|
Schedule& schedule,
|
|
Action::State& actionState,
|
|
SummaryState& summaryState,
|
|
BlackoilWellModelGeneric<Scalar, IndexTraits>& wellModel,
|
|
Parallel::Communication comm)
|
|
: ecl_state_(ecl_state)
|
|
, schedule_(schedule)
|
|
, actionState_(actionState)
|
|
, summaryState_(summaryState)
|
|
, wellModel_(wellModel)
|
|
, comm_(comm)
|
|
{}
|
|
|
|
template<typename Scalar, typename IndexTraits>
|
|
void ActionHandler<Scalar, IndexTraits>::
|
|
applyActions(const int reportStep,
|
|
const double sim_time,
|
|
const TransFunc& transUp)
|
|
{
|
|
OPM_TIMEBLOCK(applyActions);
|
|
const auto& actions = schedule_[reportStep].actions();
|
|
if (actions.empty()) {
|
|
return;
|
|
}
|
|
|
|
const Action::Context context{ summaryState_, schedule_[reportStep].wlist_manager() };
|
|
|
|
const auto now = TimeStampUTC{ schedule_.getStartTime() } + std::chrono::duration<double>(sim_time);
|
|
const auto ts = formatActionDate(now, reportStep);
|
|
|
|
|
|
SimulatorUpdate sim_update;
|
|
for (const auto& pyaction : actions.pending_python(actionState_)) {
|
|
// The std::unordered_map<std::string, Scalar> wellpi contains the well production indices from the last
|
|
// timestep. This map is needed for the keyword WELPI. For a PyAction, we do not know which wells are
|
|
// affected by the PyAction, thus we get the production indices for all wells.
|
|
const std::vector<std::string> wellpi_wells = schedule_[reportStep].well_order().names();
|
|
const auto wellpi = fetchWellPI<Scalar>(this->wellModel_, wellpi_wells, this->comm_);
|
|
auto sim_update_current = schedule_.runPyAction(reportStep, *pyaction, actionState_,
|
|
ecl_state_, summaryState_, wellpi);
|
|
|
|
if (const auto pyRes = this->actionState_.python_result(pyaction->name());
|
|
!pyRes.has_value() || !*pyRes)
|
|
{
|
|
logInactivePyAction(pyaction->name(), ts);
|
|
continue;
|
|
}
|
|
else {
|
|
logActivePyAction(pyaction->name(), ts);
|
|
}
|
|
|
|
sim_update.append(sim_update_current);
|
|
}
|
|
|
|
auto non_triggered = 0;
|
|
const auto simTime = asTimeT(now);
|
|
for (const auto& action : actions.pending(this->actionState_, simTime)) {
|
|
const auto actionResult = action->eval(context);
|
|
if (! actionResult.conditionSatisfied()) {
|
|
++non_triggered;
|
|
logInactiveAction(action->name(), ts);
|
|
continue;
|
|
}
|
|
|
|
const auto& matches = actionResult.matches();
|
|
|
|
logActiveAction(action->name(), matches.wells(), ts);
|
|
|
|
// The std::unordered_map<std::string, Scalar> wellpi contains the well production indices from the last
|
|
// timestep. This map is needed for the keyword WELPI. For an ActionX, we know which wells are affected by
|
|
// the ActionX, thus fetch the well production indices only for the affected wells.
|
|
const std::vector<std::string> wellpi_wells = action->wellpi_wells(schedule_.wellMatcher(reportStep), matches);
|
|
const auto wellpi = fetchWellPI<Scalar>(this->wellModel_, wellpi_wells, this->comm_);
|
|
|
|
const auto sim_update_current = this->schedule_
|
|
.applyAction(reportStep, *action, matches, wellpi, true);
|
|
|
|
sim_update.append(sim_update_current);
|
|
this->actionState_.add_run(*action, simTime, actionResult);
|
|
}
|
|
|
|
if (non_triggered > 0) {
|
|
logInactiveActions(non_triggered, ts);
|
|
}
|
|
|
|
bool commit_wellstate = false;
|
|
this->applySimulatorUpdate(reportStep, sim_update, transUp, commit_wellstate);
|
|
// The well state has been stored in a previous object when the time
|
|
// step has completed successfully, the action process might have
|
|
// modified the well state, and to be certain that is not overwritten
|
|
// when starting the next timestep we must commit it.
|
|
if (commit_wellstate) {
|
|
this->wellModel_.commitWGState();
|
|
}
|
|
}
|
|
|
|
template<typename Scalar, typename IndexTraits>
|
|
void ActionHandler<Scalar, IndexTraits>::
|
|
applySimulatorUpdate(const int report_step,
|
|
const SimulatorUpdate& sim_update,
|
|
const TransFunc& updateTrans,
|
|
bool& commit_wellstate)
|
|
{
|
|
OPM_TIMEBLOCK(applySimulatorUpdate);
|
|
|
|
this->wellModel_.updateEclWells(report_step, sim_update, this->summaryState_);
|
|
|
|
if (!sim_update.affected_wells.empty()) {
|
|
commit_wellstate = true;
|
|
}
|
|
|
|
if (sim_update.tran_update) {
|
|
const auto& keywords = schedule_[report_step].geo_keywords();
|
|
ecl_state_.apply_schedule_keywords( keywords );
|
|
eclBroadcast(comm_, ecl_state_.getTransMult() );
|
|
|
|
// re-compute transmissibility
|
|
updateTrans(true);
|
|
}
|
|
}
|
|
|
|
template<typename Scalar, typename IndexTraits>
|
|
void ActionHandler<Scalar, IndexTraits>::
|
|
evalUDQAssignments(const unsigned episodeIdx,
|
|
UDQState& udq_state)
|
|
{
|
|
this->schedule_[episodeIdx].udq()
|
|
.eval_assign(this->schedule_.wellMatcher(episodeIdx),
|
|
this->schedule_[episodeIdx].group_order(),
|
|
this->schedule_.segmentMatcherFactory(episodeIdx),
|
|
this->summaryState_,
|
|
udq_state);
|
|
}
|
|
|
|
template class ActionHandler<double, BlackOilDefaultFluidSystemIndices>;
|
|
|
|
#if FLOW_INSTANTIATE_FLOAT
|
|
template class ActionHandler<float, BlackOilDefaultFluidSystemIndices>;
|
|
#endif
|
|
|
|
} // namespace Opm
|