Track Pending Assignments in UDQ Configuration Object

The existing implementation used the UDQ State object to track
pending ASSIGN operations, mainly in terms of the report step index,
but this implies that the logic implicitly assumes an ASSIGN
operation can run at most once per report step.  That assumption
usually holds, but fails if the ASSIGN operation is triggered from
an ACTIONX block that happens to run multiple times within a report
step.

This commit instead introduces a new data member,

    UDQConfig::pending_assignments_

that keeps track of all ASSIGN operations that have been added for
the current report step.  We clear those pending assignments when
forming a new Schedule block (ScheduleState object), under the
assumption that all pending ASSIGN operations have been affected at
the previous time level (report step).

In effect, this new data member assumes the role of

    UDQState::assignments

and we therefore remove that data member and update the signature of

    UDQState::add_assign()

to account for the fact that the 'report_step' parameter is no
longer needed.
This commit is contained in:
Bård Skaflestad
2023-06-28 13:13:53 +02:00
parent 8ed1b4c21e
commit 09ffacb5bf
8 changed files with 120 additions and 86 deletions

View File

@@ -101,6 +101,8 @@ namespace Opm {
const std::vector<std::string>& expression,
std::size_t report_step);
bool clear_pending_assignments();
void eval_assign(std::size_t report_step,
const Schedule& sched,
const WellMatcher& wm,
@@ -147,6 +149,7 @@ namespace Opm {
serializer(units);
serializer(input_index);
serializer(type_count);
serializer(pending_assignments_);
// The UDQFunction table is constant up to udq_params, so we can
// just construct a new instance here.
@@ -174,12 +177,13 @@ namespace Opm {
OrderedMap<UDQIndex> input_index;
std::map<UDQVarType, std::size_t> type_count;
mutable std::vector<std::string> pending_assignments_{};
void add_node(const std::string& quantity, UDQAction action);
UDQAction action_type(const std::string& udq_key) const;
void eval_assign(std::size_t report_step,
const Schedule& sched,
UDQState& udq_state,
UDQContext& context) const;
void eval_define(std::size_t report_step,

View File

@@ -59,7 +59,7 @@ namespace Opm {
std::optional<double> get_segment_var(const std::string& well, const std::string& var, std::size_t segment) const;
void add(const std::string& key, double value);
void update_assign(std::size_t report_step, const std::string& keyword, const UDQSet& udq_result);
void update_assign(const std::string& keyword, const UDQSet& udq_result);
void update_define(std::size_t report_step, const std::string& keyword, const UDQSet& udq_result);
const UDQFunctionTable& function_table() const;

View File

@@ -53,8 +53,7 @@ public:
double get_segment_var(const std::string& well, const std::string& var, const std::size_t segment) const;
void add_define(std::size_t report_step, const std::string& udq_key, const UDQSet& result);
void add_assign(std::size_t report_step, const std::string& udq_key, const UDQSet& result);
bool assign(std::size_t report_step, const std::string& udq_key) const;
void add_assign(const std::string& udq_key, const UDQSet& result);
bool define(const std::string& udq_key, const std::pair<UDQUpdate, std::size_t>& update_status) const;
double undefined_value() const;
@@ -70,7 +69,6 @@ public:
serializer(this->well_values);
serializer(this->group_values);
serializer(this->segment_values);
serializer(this->assignments);
serializer(this->defines);
}
@@ -87,7 +85,6 @@ private:
// [var][well][segment] -> double
std::unordered_map<std::string, std::unordered_map<std::string, std::unordered_map<std::size_t, double>>> segment_values{};
std::unordered_map<std::string, std::size_t> assignments;
std::unordered_map<std::string, std::size_t> defines;
void add(const std::string& udq_key, const UDQSet& result);

View File

@@ -21,8 +21,8 @@
#include <opm/input/eclipse/Schedule/Action/Actions.hpp>
#include <opm/input/eclipse/Schedule/GasLiftOpt.hpp>
#include <opm/input/eclipse/Schedule/Group/GConSump.hpp>
#include <opm/input/eclipse/Schedule/Group/GConSale.hpp>
#include <opm/input/eclipse/Schedule/Group/GConSump.hpp>
#include <opm/input/eclipse/Schedule/Group/GroupEconProductionLimits.hpp>
#include <opm/input/eclipse/Schedule/Group/GuideRateConfig.hpp>
#include <opm/input/eclipse/Schedule/Network/Balance.hpp>
@@ -31,39 +31,51 @@
#include <opm/input/eclipse/Schedule/RPTConfig.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQActive.hpp>
#include <opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp>
#include <opm/input/eclipse/Schedule/VFPProdTable.hpp>
#include <opm/input/eclipse/Schedule/VFPInjTable.hpp>
#include <opm/input/eclipse/Schedule/VFPProdTable.hpp>
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
#include <opm/input/eclipse/Schedule/Well/WellMatcher.hpp>
#include <opm/input/eclipse/Schedule/Well/WellTestConfig.hpp>
#include <fmt/format.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <ctime>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace Opm {
#include <fmt/format.h>
namespace {
/*
This is to ensure that only time_points which can be represented with
std::time_t are used. The reason for clamping to std::time_t resolution is
that the serialization code in
opm-simulators:opm/simulators/utils/ParallelRestart.cpp goes via std::time_t.
*/
time_point clamp_time(time_point t) {
return TimeService::from_time_t( TimeService::to_time_t( t ) );
}
// This is to ensure that only time_points which can be represented with
// std::time_t are used. The reason for clamping to std::time_t
// resolution is that the serialization code in
// opm-simulators:opm/simulators/utils/ParallelRestart.cpp goes via
// std::time_t.
Opm::time_point clamp_time(Opm::time_point t)
{
return Opm::TimeService::from_time_t(Opm::TimeService::to_time_t(t));
}
std::pair<std::size_t, std::size_t> date_diff(const time_point& t2, const time_point& t1) {
auto ts1 = TimeStampUTC(TimeService::to_time_t(t1));
auto ts2 = TimeStampUTC(TimeService::to_time_t(t2));
auto year_diff = ts2.year() - ts1.year();
auto month_diff = year_diff*12 + ts2.month() - ts1.month();
return { year_diff, month_diff };
}
std::pair<std::size_t, std::size_t>
date_diff(const Opm::time_point& t2, const Opm::time_point& t1)
{
const auto ts1 = Opm::TimeStampUTC { Opm::TimeService::to_time_t(t1) };
const auto ts2 = Opm::TimeStampUTC { Opm::TimeService::to_time_t(t2) };
}
const auto year_diff = ts2.year() - ts1.year();
const auto month_diff = year_diff*12 + ts2.month() - ts1.month();
return { year_diff, month_diff };
}
} // Anonymous namespace
namespace Opm {
void ScheduleState::updateSAVE(bool save) {
this->m_save_step = save;
@@ -73,27 +85,23 @@ bool ScheduleState::save() const {
return this->m_save_step;
}
ScheduleState::ScheduleState(const time_point& t1):
m_start_time(clamp_time(t1)),
m_first_in_month(true),
m_first_in_year(true)
ScheduleState::ScheduleState(const time_point& t1)
: m_start_time(clamp_time(t1))
, m_first_in_month(true)
, m_first_in_year(true)
{
auto ts1 = TimeStampUTC(TimeService::to_time_t(this->m_start_time));
this->m_month_num = ts1.month() - 1;
}
ScheduleState::ScheduleState(const time_point& start_time, const time_point& end_time) :
ScheduleState(start_time)
ScheduleState::ScheduleState(const time_point& start_time, const time_point& end_time)
: ScheduleState(start_time)
{
this->m_end_time = clamp_time(end_time);
}
void ScheduleState::update_date(const time_point& prev_time) {
void ScheduleState::update_date(const time_point& prev_time)
{
auto [year_diff, month_diff] = date_diff(this->m_start_time, prev_time);
this->m_year_num += year_diff;
this->m_first_in_month = (month_diff > 0);
@@ -103,11 +111,8 @@ void ScheduleState::update_date(const time_point& prev_time) {
this->m_month_num = ts1.month() - 1;
}
ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_time) :
ScheduleState(src)
ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_time)
: ScheduleState { src } // Copy constructor
{
this->m_start_time = clamp_time(start_time);
this->m_end_time = std::nullopt;
@@ -118,15 +123,22 @@ ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_t
this->target_wellpi.clear();
this->m_save_step = false;
auto next_rft = this->rft_config().next();
if (next_rft.has_value())
this->rft_config.update( std::move(*next_rft) );
{
auto next_rft = this->rft_config().next();
this->update_date(src.m_start_time);
if (this->rst_config().save) {
auto new_rst = this->rst_config();
new_rst.save = false;
this->rst_config.update( std::move(new_rst) );
if (next_rft.has_value()) {
this->rft_config.update(std::move(*next_rft));
}
}
{
this->update_date(src.m_start_time);
if (this->rst_config().save) {
auto new_rst = this->rst_config();
new_rst.save = false;
this->rst_config.update(std::move(new_rst));
}
}
if (this->next_tstep.has_value()) {
@@ -136,16 +148,26 @@ ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_t
// Need to signal an event also for the persistance to take effect
this->events().addEvent(ScheduleEvents::TUNING_CHANGE);
}
{
auto new_udq = this->udq();
if (new_udq.clear_pending_assignments()) {
// New report step. All ASSIGNments from previous report steps
// have been performed.
this->udq.update(std::move(new_udq));
}
}
}
ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_time, const time_point& end_time) :
ScheduleState(src, start_time)
ScheduleState::ScheduleState(const ScheduleState& src,
const time_point& start_time,
const time_point& end_time)
: ScheduleState { src, start_time }
{
this->m_end_time = end_time;
}
time_point ScheduleState::start_time() const {
return this->m_start_time;
}

View File

@@ -176,6 +176,7 @@ namespace Opm {
result.units = {{"test3", "test4"}};
result.input_index.insert({"test5", UDQIndex::serializationTestObject()});
result.type_count = {{UDQVarType::SCALAR, 5}};
result.pending_assignments_.push_back("test2");
return result;
}
@@ -219,6 +220,10 @@ namespace Opm {
this->add_named_assign(quantity, selector, value, report_step);
break;
}
if (this->m_assignments.find(quantity) != this->m_assignments.end()) {
this->pending_assignments_.push_back(quantity);
}
}
void UDQConfig::add_assign(const std::string& quantity,
@@ -333,6 +338,14 @@ namespace Opm {
}
}
bool UDQConfig::clear_pending_assignments()
{
const auto update = ! this->pending_assignments_.empty();
this->pending_assignments_.clear();
return update;
}
const UDQAssign& UDQConfig::assign(const std::string& key) const
{
return this->m_assignments.at(key);
@@ -522,14 +535,18 @@ namespace Opm {
&& (this->units == data.units)
&& (this->input_index == data.input_index)
&& (this->type_count == data.type_count)
&& (this->pending_assignments_ == data.pending_assignments_)
;
}
void UDQConfig::eval_assign(const std::size_t report_step,
const Schedule& sched,
UDQState& udq_state,
UDQContext& context) const
{
if (this->pending_assignments_.empty()) {
return; // Nothing to do
}
const auto handlers = std::map<UDQVarType, EvalAssign> {
{ UDQVarType::FIELD_VAR , EvalAssign::field() },
{ UDQVarType::GROUP_VAR , EvalAssign::group(report_step, sched) },
@@ -537,24 +554,30 @@ namespace Opm {
{ UDQVarType::SEGMENT_VAR, EvalAssign::segment(context) },
};
for (const auto& index_pair : this->input_index) {
const auto& keyword = index_pair.first;
auto asgn_pos = this->m_assignments.find(keyword);
if ((asgn_pos == this->m_assignments.end()) ||
! udq_state.assign(asgn_pos->second.report_step(), keyword))
{
// No such ASSIGN or ASSIGN not active
// Recall: pending_assignments_ is mutable.
auto pending = std::vector<std::string>{};
this->pending_assignments_.swap(pending);
{
std::sort(pending.begin(), pending.end());
auto u = std::unique(pending.begin(), pending.end());
pending.erase(u, pending.end());
}
for (const auto& assignment : pending) {
auto asgn_pos = this->m_assignments.find(assignment);
if (asgn_pos == this->m_assignments.end()) {
// No such ASSIGNment. Unexpected.
continue;
}
auto handler = handlers.find(asgn_pos->second.var_type());
if (handler == handlers.end()) {
// Unhandled variable type.
// Unhandled/unsupported variable type.
continue;
}
context.update_assign(report_step, keyword,
handler->second(asgn_pos->second));
context.update_assign(assignment, handler->second(asgn_pos->second));
}
}
@@ -607,7 +630,7 @@ namespace Opm {
UDQContext context {
this->function_table(), wm, std::move(create_segment_matcher), st, udq_state
};
this->eval_assign(report_step, sched, udq_state, context);
this->eval_assign(report_step, sched, context);
this->eval_define(report_step, udq_state, context);
}
@@ -621,7 +644,7 @@ namespace Opm {
UDQContext context {
this->function_table(), wm, std::move(create_segment_matcher), st, udq_state
};
this->eval_assign(report_step, sched, udq_state, context);
this->eval_assign(report_step, sched, context);
}
void UDQConfig::required_summary(std::unordered_set<std::string>& summary_keys) const

View File

@@ -218,11 +218,10 @@ namespace Opm {
return this->udqft;
}
void UDQContext::update_assign(const std::size_t report_step,
const std::string& keyword,
void UDQContext::update_assign(const std::string& keyword,
const UDQSet& udq_result)
{
this->udq_state.add_assign(report_step, keyword, udq_result);
this->udq_state.add_assign(keyword, udq_result);
this->summary_state.update_udq(udq_result, this->udq_state.undefined_value());
}

View File

@@ -291,9 +291,8 @@ void UDQState::add_define(std::size_t report_step, const std::string& udq_key, c
this->add(udq_key, result);
}
void UDQState::add_assign(std::size_t report_step, const std::string& udq_key, const UDQSet& result)
void UDQState::add_assign(const std::string& udq_key, const UDQSet& result)
{
this->assignments[udq_key] = report_step;
this->add(udq_key, result);
}
@@ -364,7 +363,6 @@ bool UDQState::operator==(const UDQState& other) const
&& (this->well_values == other.well_values)
&& (this->group_values == other.group_values)
&& (this->segment_values == other.segment_values)
&& (this->assignments == other.assignments)
&& (this->defines == other.defines);
}
@@ -373,7 +371,6 @@ UDQState UDQState::serializationTestObject()
UDQState st;
st.undef_value = 78;
st.scalar_values = {{"FU1", 100}, {"FU2", 200}};
st.assignments = {{"GU1", 99}, {"GU2", 199}};
st.defines = {{"DU1", 299}, {"DU2", 399}};
st.well_values.emplace("W1", std::unordered_map<std::string, double>{{"U1", 100}, {"U2", 200}});
@@ -410,14 +407,6 @@ UDQState UDQState::serializationTestObject()
return st;
}
bool UDQState::assign(std::size_t report_step, const std::string& udq_key) const
{
auto assign_iter = this->assignments.find(udq_key);
return (assign_iter == this->assignments.end())
|| (report_step > assign_iter->second);
}
bool UDQState::define(const std::string& udq_key,
const std::pair<UDQUpdate, std::size_t>& update_status) const
{

View File

@@ -2851,7 +2851,7 @@ UDQ
const auto& udq = schedule.getUDQConfig(1);
{
const auto& ass = udq.assign("FUBHPP1");
context.update_assign(1, "FUBHPP1", ass.eval());
context.update_assign("FUBHPP1", ass.eval());
}
const auto& def = udq.define("WUDELTA");
auto res = def.eval(context);