Merge pull request #656 from joakim-hove/udq-parse

UDQ: Complete well related UDQ's
This commit is contained in:
Joakim Hove 2019-03-14 13:45:55 +01:00 committed by GitHub
commit fa77187871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1968 additions and 206 deletions

View File

@ -124,16 +124,18 @@ if(ENABLE_ECL_INPUT)
src/opm/parser/eclipse/EclipseState/Tables/TableManager.cpp
src/opm/parser/eclipse/EclipseState/Tables/TableSchema.cpp
src/opm/parser/eclipse/EclipseState/Tables/Tables.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQASTNode.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQParams.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQParser.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQSet.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQWellSet.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQAssign.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQDefine.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQInput.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQContext.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunction.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.cpp
src/opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQExpression.cpp
src/opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.cpp
src/opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.cpp
src/opm/parser/eclipse/Parser/ErrorGuard.cpp
@ -519,6 +521,7 @@ if(ENABLE_ECL_INPUT)
opm/parser/eclipse/EclipseState/checkDeck.hpp
opm/parser/eclipse/EclipseState/Runspec.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQAssign.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQDefine.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQContext.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQInput.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp
@ -528,7 +531,6 @@ if(ENABLE_ECL_INPUT)
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQSet.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunction.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.hpp
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQExpression.hpp
opm/parser/eclipse/Deck/DeckItem.hpp
opm/parser/eclipse/Deck/Deck.hpp
opm/parser/eclipse/Deck/Section.hpp

View File

@ -21,18 +21,25 @@
#ifndef UDQ_CONTEXT_HPP
#define UDQ_CONTEXT_HPP
#include <vector>
#include <string>
#include <unordered_map>
namespace Opm {
class SummaryState;
class UDQFunctionTable;
class UDQContext{
public:
explicit UDQContext(const SummaryState& summary_state);
UDQContext(const UDQFunctionTable& udqft, const SummaryState& summary_state);
double get(const std::string& key) const;
double get_well_var(const std::string& well, const std::string& var) const;
void add(const std::string& key, double value);
const UDQFunctionTable& function_table() const;
std::vector<std::string> wells() const;
private:
const UDQFunctionTable& udqft;
const SummaryState& summary_state;
std::unordered_map<std::string, double> values;
};

View File

@ -0,0 +1,76 @@
/*
Copyright 2018 Statoil ASA.
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 <http://www.gnu.org/licenses/>.
*/
#ifndef UDQ_DEFINE_HPP
#define UDQ_DEFINE_HPP
#include <string>
#include <vector>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQWellSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQContext.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.hpp>
namespace Opm {
class UDQASTNode;
class ParseContext;
class ErrorGuard;
class UDQDefine{
public:
UDQDefine(const UDQParams& udq_params,
const std::string& keyword,
const std::vector<std::string>& deck_data);
UDQDefine(const UDQParams& udq_params,
const std::string& keyword,
const std::vector<std::string>& deck_data,
const ParseContext& parseContext,
ErrorGuard& errors);
template <typename T>
UDQDefine(const UDQParams& udq_params,
const std::string& keyword,
const std::vector<std::string>& deck_data,
const ParseContext& parseContext,
T&& errors);
UDQWellSet eval_wells(const UDQContext& context) const;
UDQSet eval(const UDQContext& context) const;
const std::string& keyword() const;
/*
Should not be internalized at all - and will go away; but temporarily needed for testing.
*/
std::vector<std::string> tokens;
UDQVarType var_type() const;
private:
const UDQParams& udq_params; // Beacuse of the shared RNG stream this must be a reference.
std::string m_keyword;
std::shared_ptr<UDQASTNode> ast;
UDQVarType m_var_type;
};
}
#endif

View File

@ -35,7 +35,67 @@ enum class UDQVarType {
};
enum class UDQAction {ASSIGN, DEFINE, UNITS, UPDATE};
enum class UDQTokenType{
error = 0,
number = 1,
open_paren = 2,
close_paren = 3,
ecl_expr = 7,
//
binary_op_add = 8,
binary_op_sub = 9,
binary_op_div = 10,
binary_op_mul = 11,
binary_op_pow = 12,
binary_op_uadd = 13,
binary_op_umul = 14,
binary_op_umin = 15,
binary_op_umax = 16,
binary_cmp_eq = 17,
binary_cmp_ne = 18,
binary_cmp_le = 19,
binary_cmp_ge = 20,
binary_cmp_lt = 21,
binary_cmp_gt = 22,
//
elemental_func_randn = 23,
elemental_func_randu = 24,
elemental_func_rrandn = 25,
elemental_func_rrandu = 26,
elemental_func_abs = 27,
elemental_func_def = 28,
elemental_func_exp = 29,
elemental_func_idv = 30,
elemental_func_ln = 31,
elemental_func_log = 32,
elemental_func_nint = 33,
elemental_func_sorta = 34,
elemental_func_sortd = 35,
elemental_func_undef = 36,
//
scalar_func_sum = 37,
scalar_func_avea = 38,
scalar_func_aveg = 39,
scalar_func_aveh = 40,
scalar_func_max = 41,
scalar_func_min = 42,
scalar_func_norm1 = 43,
scalar_func_norm2 = 44,
scalar_func_normi = 45,
scalar_func_prod = 46,
//
table_lookup = 47,
//
end = 100
};
enum class UDQAction {
ASSIGN,
DEFINE,
UNITS,
UPDATE};
@ -43,7 +103,11 @@ namespace UDQ {
UDQVarType varType(const std::string& keyword);
UDQAction actionType(const std::string& action_string);
UDQTokenType funcType(const std::string& func_name);
bool binaryFunc(UDQTokenType token_type);
bool elementalUnaryFunc(UDQTokenType token_type);
bool scalarFunc(UDQTokenType token_type);
bool cmpFunc(UDQTokenType token_type);
}
}

View File

@ -26,17 +26,20 @@
#include <random>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
namespace Opm {
class UDQFunction {
public:
explicit UDQFunction(const std::string& name);
UDQFunction(const std::string& name);
virtual ~UDQFunction() = default;
const std::string& name() const;
UDQTokenType type() const;
private:
std::string m_name;
UDQTokenType func_type;
};
class UDQScalarFunction : public UDQFunction {

View File

@ -30,7 +30,8 @@ namespace Opm {
class UDQFunctionTable {
public:
UDQFunctionTable(UDQParams& params);
explicit UDQFunctionTable(const UDQParams& params);
UDQFunctionTable();
bool has_function(const std::string& name) const;
const UDQFunction& get(const std::string& name) const;
private:

View File

@ -25,26 +25,38 @@
#include <unordered_map>
#include <unordered_set>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQExpression.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQDefine.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQAssign.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQParams.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.hpp>
namespace Opm {
class DeckRecord;
class Deck;
class UDQInput {
public:
void add_record(const DeckRecord& record);
const std::vector<UDQExpression>& expressions() const noexcept;
explicit UDQInput(const Deck& deck);
const std::string& unit(const std::string& key) const;
bool has_unit(const std::string& keyword) const;
bool has_keyword(const std::string& keyword) const;
void add_record(const DeckRecord& record);
void assign_unit(const std::string& keyword, const std::string& unit);
const std::vector<UDQDefine>& definitions() const;
std::vector<UDQDefine> definitions(UDQVarType var_type) const;
const std::vector<UDQAssign>& assignments() const;
std::vector<UDQAssign> assignments(UDQVarType var_type) const;
const UDQFunctionTable& function_table() const;
private:
std::vector<UDQExpression> m_expressions;
UDQParams udq_params;
UDQFunctionTable udqft;
std::vector<UDQDefine> m_definitions;
std::vector<UDQAssign> m_assignments;
std::unordered_map<std::string, std::string> units;
std::unordered_set<std::string> keywords;

View File

@ -247,6 +247,7 @@ namespace Opm {
*/
const static std::string UNSUPPORTED_TERMINATE_IF_BHP;
const static std::string UDQ_PARSE_ERROR;
/*
If the third item in the THPRES keyword is defaulted the

View File

@ -1122,11 +1122,12 @@ bool is_udq(const std::string& keyword) {
void eval_udq(const Schedule& schedule, std::size_t sim_step, SummaryState& st)
{
const UDQInput& udq = schedule.getUDQConfig(sim_step);
const auto& func_table = udq.function_table();
UDQContext context(func_table, st);
std::vector<std::string> wells;
for (const auto* well : schedule.getWells())
wells.push_back(well->name());
for (const auto& assign : udq.assignments(UDQVarType::WELL_VAR)) {
auto ws = assign.eval_wells(wells);
for (const auto& well : wells) {
@ -1135,9 +1136,16 @@ void eval_udq(const Schedule& schedule, std::size_t sim_step, SummaryState& st)
st.add_well_var(well, ws.name(), udq_value.value());
}
}
for (const auto& def : udq.definitions(UDQVarType::WELL_VAR)) {
auto ws = def.eval_wells(context);
for (const auto& well : wells) {
const auto& udq_value = ws[well];
if (udq_value)
st.add_well_var(well, def.keyword(), udq_value.value());
}
}
}
}
namespace out {
@ -1504,10 +1512,7 @@ void Summary::add_timestep( int report_step,
}
}
{
UDQContext udq_context(st);
eval_udq(schedule, sim_step, st);
}
eval_udq(schedule, sim_step, st);
{
const ecl_sum_type * ecl_sum = this->ecl_sum.get();
const ecl_smspec_type * smspec = ecl_sum_get_smspec(ecl_sum);

View File

@ -83,7 +83,7 @@ namespace Opm {
m_runspec( runspec ),
wtest_config(this->m_timeMap, std::make_shared<WellTestConfig>() ),
wlist_manager( this->m_timeMap, std::make_shared<WListManager>()),
udq_config(this->m_timeMap, std::make_shared<UDQInput>())
udq_config(this->m_timeMap, std::make_shared<UDQInput>(deck))
{
m_controlModeWHISTCTL = WellProducer::CMODE_UNDEFINED;
addGroup( "FIELD", 0 );
@ -943,7 +943,6 @@ namespace Opm {
void Schedule::handleUDQ(const DeckKeyword& keyword, size_t currentStep) {
const auto& current = *this->udq_config.get(currentStep);
std::shared_ptr<UDQInput> new_udq = std::make_shared<UDQInput>(current);
for (const auto& record : keyword)
new_udq->add_record(record);

View File

@ -0,0 +1,146 @@
/*
Copyright 2019 Equinor ASA.
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 <http://www.gnu.org/licenses/>.
*/
#include <fnmatch.h>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunction.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
#include "UDQASTNode.hpp"
namespace Opm {
UDQASTNode::UDQASTNode() :
type(UDQTokenType::error)
{}
UDQASTNode::UDQASTNode(UDQTokenType type) :
type(type)
{
if (type == UDQTokenType::error)
return;
if (type == UDQTokenType::end)
return;
throw std::invalid_argument("The one argument constructor is only available for error and end");
}
UDQASTNode::UDQASTNode(double scalar_value) :
type(UDQTokenType::number),
scalar_value(scalar_value)
{}
UDQASTNode::UDQASTNode(UDQTokenType type_arg,
const std::string& func_name,
const UDQASTNode& arg) :
type(type_arg),
string_value(func_name)
{
this->arglist.push_back(arg);
}
UDQASTNode::UDQASTNode(UDQTokenType type_arg,
const std::string& func_name,
const UDQASTNode& left,
const UDQASTNode& right) :
type(type_arg),
string_value(func_name)
{
this->arglist.push_back(left);
this->arglist.push_back(right);
}
UDQASTNode::UDQASTNode(UDQTokenType type_arg,
const std::string& string_value,
const std::vector<std::string>& selector) :
type(type_arg),
string_value(string_value),
selector(selector)
{
if (type_arg == UDQTokenType::number)
this->scalar_value = std::stod(string_value);
}
UDQWellSet UDQASTNode::eval_wells(const UDQContext& context) {
const auto& wells = context.wells();
if (this->type == UDQTokenType::ecl_expr) {
auto res = UDQWellSet(this->string_value, wells);
if (this->selector.size() > 0) {
int fnmatch_flags = 0;
const std::string& well_pattern = this->selector[0];
if (well_pattern.find("*") == std::string::npos)
throw std::invalid_argument("When evaluating a well UDQ you can not use fully qualified well variables");
for (const auto& well : wells) {
if (fnmatch(well_pattern.c_str(), well.c_str(), fnmatch_flags) == 0)
res.assign(well, context.get_well_var(well, this->string_value));
}
} else {
for (const auto& well : wells)
res.assign(well, context.get_well_var(well, this->string_value));
}
return res;
}
if (UDQ::scalarFunc(this->type))
throw std::invalid_argument("Can not invoke scalar function for well set");
if (UDQ::elementalUnaryFunc(this->type)) {
auto input_arg = this->arglist[0];
auto func_arg = input_arg.eval_wells(context);
const auto& udqft = context.function_table();
const UDQUnaryElementalFunction& func = dynamic_cast<const UDQUnaryElementalFunction&>(udqft.get(this->string_value));
auto udq_set = func.eval(func_arg);
return UDQWellSet(this->string_value, wells, udq_set);
}
if (UDQ::binaryFunc(this->type)) {
auto left_arg = this->arglist[0].eval_wells(context);
auto right_arg = this->arglist[1].eval_wells(context);
const auto& udqft = context.function_table();
const UDQBinaryFunction& func = dynamic_cast<const UDQBinaryFunction&>(udqft.get(this->string_value));
auto udq_set = func.eval(left_arg, right_arg);
return UDQWellSet(this->string_value, wells, udq_set);
}
if (this->type == UDQTokenType::number)
return UDQWellSet(this->string_value, wells, this->scalar_value);
throw std::invalid_argument("UDQASTNode::eval_wells: not implemented function type: " + std::to_string(static_cast<int>(this->type)));
}
}

View File

@ -0,0 +1,55 @@
/*
Copyright 2019 Equinor ASA.
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 <http://www.gnu.org/licenses/>.
*/
#ifndef UDQASTNODE_HPP
#define UDQASTNODE_HPP
#include <string>
#include <vector>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQWellSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQContext.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
namespace Opm {
class UDQASTNode {
public:
UDQASTNode();
UDQASTNode(UDQTokenType type_arg);
UDQASTNode(double scalar_value);
UDQASTNode(UDQTokenType type_arg, const std::string& string_value, const std::vector<std::string>& selector);
UDQASTNode(UDQTokenType type_arg, const std::string& func_name, const UDQASTNode& arg);
UDQASTNode(UDQTokenType type_arg, const std::string& func_name, const UDQASTNode& left, const UDQASTNode& right);
UDQWellSet eval_wells(const UDQContext& context);
UDQTokenType type;
private:
std::string string_value;
double scalar_value;
std::vector<std::string> selector;
std::vector<UDQASTNode> arglist;
};
}
#endif

View File

@ -47,7 +47,10 @@ UDQVarType UDQAssign::var_type() const {
}
UDQWellSet UDQAssign::eval_wells(const std::vector<std::string>& wells) const {
UDQWellSet ws(m_keyword, wells);
if (this->m_var_type != UDQVarType::WELL_VAR)
throw std::runtime_error("This is a bug - eval_wells called for UDQ variable which is not a well quantity.");
UDQWellSet ws(this->m_keyword, wells);
if (this->m_selector.empty())
ws.assign(this->m_value);

View File

@ -24,7 +24,8 @@
namespace Opm {
UDQContext::UDQContext(const SummaryState& summary_state) :
UDQContext::UDQContext(const UDQFunctionTable& udqft, const SummaryState& summary_state) :
udqft(udqft),
summary_state(summary_state)
{
for (const auto& pair : TimeMap::eclipseMonthIndices())
@ -57,4 +58,17 @@ namespace Opm {
return pair_ptr->second;
}
double UDQContext::get_well_var(const std::string& well, const std::string& var) const {
return this->summary_state.get_well_var(well, var);
}
std::vector<std::string> UDQContext::wells() const {
return this->summary_state.wells();
}
const UDQFunctionTable& UDQContext::function_table() const {
return this->udqft;
}
}

View File

@ -0,0 +1,151 @@
/*
Copyright 2018 Statoil ASA.
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 <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <cstring>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/ErrorGuard.hpp>
#include <opm/parser/eclipse/RawDeck/RawConsts.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQDefine.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
#include "UDQParser.hpp"
#include "UDQASTNode.hpp"
namespace Opm {
namespace {
std::vector<std::string> quote_split(const std::string& item) {
char quote_char = '\'';
std::vector<std::string> items;
std::size_t offset = 0;
while (true) {
auto quote_pos1 = item.find(quote_char, offset);
if (quote_pos1 == std::string::npos) {
items.push_back(item.substr(offset));
break;
}
auto quote_pos2 = item.find(quote_char, quote_pos1 + 1);
if (quote_pos2 == std::string::npos)
throw std::invalid_argument("Unbalanced quotes in: " + item);
if (quote_pos1 > offset)
items.push_back(item.substr(offset, quote_pos1 - offset));
items.push_back(item.substr(quote_pos1, 1 + quote_pos2 - quote_pos1));
offset = quote_pos2 + 1;
}
return items;
}
}
template <typename T>
UDQDefine::UDQDefine(const UDQParams& udq_params,
const std::string& keyword,
const std::vector<std::string>& deck_data,
const ParseContext& parseContext,
T&& errors) :
UDQDefine(udq_params, keyword, deck_data, parseContext, errors)
{}
UDQDefine::UDQDefine(const UDQParams& udq_params,
const std::string& keyword,
const std::vector<std::string>& deck_data) :
UDQDefine(udq_params, keyword, deck_data, ParseContext(), ErrorGuard())
{}
UDQDefine::UDQDefine(const UDQParams& udq_params,
const std::string& keyword,
const std::vector<std::string>& deck_data,
const ParseContext& parseContext,
ErrorGuard& errors) :
udq_params(udq_params),
m_keyword(keyword),
m_var_type(UDQ::varType(keyword))
{
std::vector<std::string> tokens;
for (const std::string& deck_item : deck_data) {
for (const std::string& item : quote_split(deck_item)) {
if (RawConsts::is_quote()(item[0])) {
tokens.push_back(item.substr(1, item.size() - 2));
continue;
}
const std::vector<std::string> splitters = {"TU*[]", "(", ")", "[", "]", ",", "+", "-", "/", "*", "==", "!=", "^", ">=", "<=", ">", "<"};
size_t offset = 0;
size_t pos = 0;
while (pos < item.size()) {
size_t splitter_index = 0;
while (splitter_index < splitters.size()) {
const std::string& splitter = splitters[splitter_index];
size_t find_pos = item.find(splitter, pos);
if (find_pos == pos) {
if (pos > offset)
tokens.push_back(item.substr(offset, pos - offset));
tokens.push_back(splitter);
pos = find_pos + 1;
offset = pos;
break;
}
splitter_index++;
}
if (splitter_index == splitters.size())
pos += 1;
}
if (pos > offset)
tokens.push_back(item.substr(offset, pos - offset));
}
}
this->ast = std::make_shared<UDQASTNode>( UDQParser::parse(this->udq_params, tokens, parseContext, errors) );
this->tokens = tokens;
}
UDQWellSet UDQDefine::eval_wells(const UDQContext& context) const {
return this->ast->eval_wells(context);
}
UDQSet UDQDefine::eval(const UDQContext& context) const {
switch (this->m_var_type) {
case UDQVarType::WELL_VAR:
return this->eval_wells(context);
default:
throw std::invalid_argument("UDQ subtype: not supported");
}
}
UDQVarType UDQDefine::var_type() const {
return this->m_var_type;
}
const std::string& UDQDefine::keyword() const {
return this->m_keyword;
}
}

View File

@ -18,6 +18,9 @@
*/
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <stdexcept>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
@ -25,6 +28,101 @@
namespace Opm {
namespace UDQ {
namespace {
const std::set<UDQTokenType> cmp_func = {UDQTokenType::binary_cmp_eq,
UDQTokenType::binary_cmp_ne,
UDQTokenType::binary_cmp_le,
UDQTokenType::binary_cmp_ge,
UDQTokenType::binary_cmp_lt,
UDQTokenType::binary_cmp_gt};
const std::set<UDQTokenType> binary_func = {UDQTokenType::binary_op_add,
UDQTokenType::binary_op_mul,
UDQTokenType::binary_op_sub,
UDQTokenType::binary_op_div,
UDQTokenType::binary_op_pow,
UDQTokenType::binary_op_uadd,
UDQTokenType::binary_op_umul,
UDQTokenType::binary_op_umin,
UDQTokenType::binary_op_umax,
UDQTokenType::binary_cmp_eq,
UDQTokenType::binary_cmp_ne,
UDQTokenType::binary_cmp_le,
UDQTokenType::binary_cmp_ge,
UDQTokenType::binary_cmp_lt,
UDQTokenType::binary_cmp_gt};
const std::set<UDQTokenType> scalar_func = {UDQTokenType::scalar_func_sum,
UDQTokenType::scalar_func_avea,
UDQTokenType::scalar_func_aveg,
UDQTokenType::scalar_func_aveh,
UDQTokenType::scalar_func_max,
UDQTokenType::scalar_func_min,
UDQTokenType::scalar_func_norm1,
UDQTokenType::scalar_func_norm2,
UDQTokenType::scalar_func_normi,
UDQTokenType::scalar_func_prod};
const std::set<UDQTokenType> unary_elemental_func = {UDQTokenType::elemental_func_randn,
UDQTokenType::elemental_func_randu,
UDQTokenType::elemental_func_rrandn,
UDQTokenType::elemental_func_rrandu,
UDQTokenType::elemental_func_abs,
UDQTokenType::elemental_func_def,
UDQTokenType::elemental_func_exp,
UDQTokenType::elemental_func_idv,
UDQTokenType::elemental_func_ln,
UDQTokenType::elemental_func_log,
UDQTokenType::elemental_func_nint,
UDQTokenType::elemental_func_sorta,
UDQTokenType::elemental_func_sortd,
UDQTokenType::elemental_func_undef};
const std::unordered_map<std::string, UDQTokenType> func_type = {{"+", UDQTokenType::binary_op_add},
{"-", UDQTokenType::binary_op_sub},
{"/", UDQTokenType::binary_op_div},
{"DIV", UDQTokenType::binary_op_div},
{"*", UDQTokenType::binary_op_mul},
{"^", UDQTokenType::binary_op_pow},
{"UADD", UDQTokenType::binary_op_uadd},
{"UMUL", UDQTokenType::binary_op_umul},
{"UMIN", UDQTokenType::binary_op_umin},
{"UMAX", UDQTokenType::binary_op_umax},
{"==", UDQTokenType::binary_cmp_eq},
{"!=", UDQTokenType::binary_cmp_ne},
{"<=", UDQTokenType::binary_cmp_le},
{">=", UDQTokenType::binary_cmp_ge},
{"<", UDQTokenType::binary_cmp_lt},
{">", UDQTokenType::binary_cmp_gt},
{"RANDN", UDQTokenType::elemental_func_randn},
{"RANDU", UDQTokenType::elemental_func_randu},
{"RRNDN", UDQTokenType::elemental_func_rrandn},
{"RRNDU", UDQTokenType::elemental_func_rrandu},
{"ABS", UDQTokenType::elemental_func_abs},
{"DEF", UDQTokenType::elemental_func_def},
{"EXP", UDQTokenType::elemental_func_exp},
{"IDV", UDQTokenType::elemental_func_idv},
{"LN", UDQTokenType::elemental_func_ln},
{"LOG", UDQTokenType::elemental_func_log},
{"NINT", UDQTokenType::elemental_func_nint},
{"SORTA", UDQTokenType::elemental_func_sorta},
{"SORTD", UDQTokenType::elemental_func_sortd},
{"UNDEF", UDQTokenType::elemental_func_undef},
{"SUM", UDQTokenType::scalar_func_sum},
{"AVEA", UDQTokenType::scalar_func_avea},
{"AVEG", UDQTokenType::scalar_func_aveg},
{"AVEH", UDQTokenType::scalar_func_aveh},
{"MAX", UDQTokenType::scalar_func_max},
{"MIN", UDQTokenType::scalar_func_min},
{"NORM1", UDQTokenType::scalar_func_norm1},
{"NORM2", UDQTokenType::scalar_func_norm2},
{"NORMI", UDQTokenType::scalar_func_normi},
{"PROD", UDQTokenType::scalar_func_prod}};
}
UDQVarType varType(const std::string& keyword) {
if (keyword[1] != 'U')
@ -71,5 +169,33 @@ UDQAction actionType(const std::string& action_string) {
throw std::invalid_argument("Invalid action string " + action_string);
}
bool binaryFunc(UDQTokenType token_type) {
return (binary_func.count(token_type) > 0);
}
bool scalarFunc(UDQTokenType token_type) {
return (scalar_func.count(token_type) > 0);
}
bool elementalUnaryFunc(UDQTokenType token_type) {
return (unary_elemental_func.count(token_type) > 0);
}
bool cmpFunc(UDQTokenType token_type) {
return (cmp_func.count(token_type) > 0);
}
UDQTokenType funcType(const std::string& func_name) {
if (func_type.count(func_name) > 0)
return func_type.at(func_name);
if (func_name.substr(0,2) == "TU") {
return UDQTokenType::table_lookup;
}
return UDQTokenType::error;
}
}
}

View File

@ -1,110 +0,0 @@
/*
Copyright 2018 Statoil ASA.
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 <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include <string>
#include <iostream>
#include <opm/parser/eclipse/RawDeck/RawConsts.hpp>
#include <opm/parser/eclipse/Deck/DeckRecord.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQExpression.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
namespace Opm {
/*
The tokenizer algorithm has two problems with '*':
1. When used to specify a wildcard set - like 'P*' for all well starting
with 'P' the tokenizer will interpret the '*' as a multiplication sign
and split into independent tokens 'P' and '*'.
2. For items like '2*(1+WBHP)' the parsing code will expand the 2*
operator to the repeated tokens : (1+WBHP), (1+WBHP)
*/
UDQExpression::UDQExpression(UDQAction action, const std::string& keyword_in, const std::vector<std::string>& input_data) :
m_action(action),
m_keyword(keyword_in),
m_var_type(UDQ::varType(keyword_in))
{
for (const std::string& item : input_data) {
if (RawConsts::is_quote()(item[0])) {
this->data.push_back(item.substr(1, item.size() - 2));
continue;
}
const std::vector<std::string> splitters = {"TU*[]", "(", ")", "[", "]", ",", "+", "-", "/", "*", "==", "!=", "^", ">=", "<=", ">", "<"};
size_t offset = 0;
size_t pos = 0;
while (pos < item.size()) {
size_t splitter_index = 0;
while (splitter_index < splitters.size()) {
const std::string& splitter = splitters[splitter_index];
size_t find_pos = item.find(splitter, pos);
if (find_pos == pos) {
if (pos > offset)
this->data.push_back(item.substr(offset, pos - offset));
this->data.push_back(splitter);
pos = find_pos + 1;
offset = pos;
break;
}
splitter_index++;
}
if (splitter_index == splitters.size())
pos += 1;
}
if (pos > offset)
this->data.push_back(item.substr(offset, pos - offset));
}
}
UDQExpression::UDQExpression(const DeckRecord& record) :
UDQExpression(UDQ::actionType(record.getItem("ACTION").get<std::string>(0)),
record.getItem("QUANTITY").get<std::string>(0),
record.getItem("DATA").getData<std::string>())
{
}
const std::vector<std::string>& UDQExpression::tokens() const {
return this->data;
}
UDQAction UDQExpression::action() const {
return this->m_action;
}
const std::string& UDQExpression::keyword() const {
return this->m_keyword;
}
}

View File

@ -28,10 +28,15 @@
namespace Opm {
UDQFunction::UDQFunction(const std::string& name) :
m_name(name)
m_name(name),
func_type(UDQ::funcType(name))
{
}
UDQTokenType UDQFunction::type() const {
return this->func_type;
}
const std::string& UDQFunction::name() const {
return this->m_name;
}
@ -279,7 +284,7 @@ namespace {
UDQSet udq_sort(const UDQSet& arg, std::size_t defined_size, const std::function<bool(int,int)>& cmp) {
auto result = arg;
std::vector<int> index(defined_size);
std::iota(index.begin(), index.end(), 0);
std::iota(index.begin(), index.end(), 1);
std::sort(index.begin(), index.end(), cmp);
std::size_t output_index = 0;
@ -297,12 +302,12 @@ namespace {
UDQSet UDQUnaryElementalFunction::SORTA(const UDQSet& arg) {
auto defined_values = arg.defined_values();
return udq_sort(arg, defined_values.size(), [&defined_values](int a, int b){ return defined_values[a] < defined_values[b]; });
return udq_sort(arg, defined_values.size(), [&defined_values](int a, int b){ return defined_values[a - 1] < defined_values[b - 1]; });
}
UDQSet UDQUnaryElementalFunction::SORTD(const UDQSet& arg) {
auto defined_values = arg.defined_values();
return udq_sort(arg, defined_values.size(), [&defined_values](int a, int b){ return defined_values[a] > defined_values[b]; });
return udq_sort(arg, defined_values.size(), [&defined_values](int a, int b){ return defined_values[a - 1] > defined_values[b - 1]; });
}
UDQBinaryFunction::UDQBinaryFunction(const std::string& name, std::function<UDQSet(const UDQSet& lhs, const UDQSet& rhs)> f) :

View File

@ -25,7 +25,11 @@
namespace Opm {
UDQFunctionTable::UDQFunctionTable(UDQParams& params) :
UDQFunctionTable::UDQFunctionTable() :
UDQFunctionTable(UDQParams())
{}
UDQFunctionTable::UDQFunctionTable(const UDQParams& params) :
params(params)
{
// SCalar functions
@ -73,7 +77,6 @@ UDQFunctionTable::UDQFunctionTable(UDQParams& params) :
this->insert_function( std::make_shared<UDQBinaryFunction>(">=", ge) );
this->insert_function( std::make_shared<UDQBinaryFunction>("<=", le) );
this->insert_function( std::make_shared<UDQBinaryFunction>("^", UDQBinaryFunction::POW ));
this->insert_function( std::make_shared<UDQBinaryFunction>("^", UDQBinaryFunction::POW ));
this->insert_function( std::make_shared<UDQBinaryFunction>("<", UDQBinaryFunction::LT ));
this->insert_function( std::make_shared<UDQBinaryFunction>(">", UDQBinaryFunction::GT ));

View File

@ -35,6 +35,13 @@ namespace Opm {
}
UDQInput::UDQInput(const Deck& deck) :
udq_params(deck),
udqft(this->udq_params)
{
}
void UDQInput::add_record(const DeckRecord& record) {
auto action = UDQ::actionType(record.getItem("ACTION").get<std::string>(0));
@ -48,14 +55,24 @@ namespace Opm {
double value = std::stod(data.back());
this->m_assignments.emplace_back( quantity, selector, value );
} else
this->m_expressions.emplace_back(action, quantity, data);
this->m_definitions.emplace_back(this->udq_params, quantity, data);
this->keywords.insert(quantity);
}
const std::vector<UDQExpression>& UDQInput::expressions() const noexcept {
return this->m_expressions;
const std::vector<UDQDefine>& UDQInput::definitions() const {
return this->m_definitions;
}
std::vector<UDQDefine> UDQInput::definitions(UDQVarType var_type) const {
std::vector<UDQDefine> filtered_defines;
std::copy_if(this->m_definitions.begin(),
this->m_definitions.end(),
std::back_inserter(filtered_defines),
[&var_type](const UDQDefine& def) { return def.var_type() == var_type; });
return filtered_defines;
}
@ -105,4 +122,7 @@ namespace Opm {
return (this->keywords.count(keyword) > 0);
}
const UDQFunctionTable& UDQInput::function_table() const {
return this->udqft;
}
}

View File

@ -0,0 +1,260 @@
/*
Copyright 2019 Equinor ASA.
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 <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <cstring>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include "UDQParser.hpp"
namespace Opm {
UDQTokenType UDQParser::get_type(const std::string& arg) const {
auto func_type = UDQ::funcType(arg);
if (func_type == UDQTokenType::table_lookup)
throw std::invalid_argument("Table lookup function TU*[] is not supported in UDQ");
if (func_type != UDQTokenType::error)
return func_type;
if (arg == "(")
return UDQTokenType::open_paren;
if (arg == ")")
return UDQTokenType::close_paren;
{
char * end_ptr;
std::strtod(arg.c_str(), &end_ptr);
if (std::strlen(end_ptr) == 0)
return UDQTokenType::number;
}
return UDQTokenType::ecl_expr;
}
std::size_t UDQParser::current_size() const {
if (this->tokens.size() == static_cast<std::size_t>(this->current_pos))
return 0;
if (this->current_pos < 0)
return 1;
const std::string& first_arg = this->tokens[this->current_pos];
if (this->get_type(first_arg) != UDQTokenType::ecl_expr)
return 1;
std::size_t offset = this->current_pos;
while (true) {
const std::string& arg = this->tokens[offset];
if (this->get_type(arg) != UDQTokenType::ecl_expr)
break;
offset += 1;
if (offset == this->tokens.size())
break;
}
return offset - this->current_pos;
}
UDQParseNode UDQParser::next() {
this->current_pos += this->current_size();
return this->current();
}
UDQParseNode UDQParser::current() const {
if (static_cast<size_t>(this->current_pos) == this->tokens.size())
return UDQTokenType::end;
const std::string& arg = this->tokens[this->current_pos];
auto type = this->get_type(arg);
if (type != UDQTokenType::ecl_expr)
return UDQParseNode(type, arg);
std::size_t selector_size = this->current_size() - 1;
const auto * token_ptr = std::addressof(this->tokens[this->current_pos + 1]);
std::vector<std::string> selector(token_ptr, token_ptr + selector_size);
return UDQParseNode(type, arg, selector);
}
UDQASTNode UDQParser::parse_factor() {
auto current = this->current();
if (current.type == UDQTokenType::open_paren) {
this->next();
auto inner_expr = this->parse_cmp();
current = this->current();
if (current.type != UDQTokenType::close_paren)
return UDQTokenType::error;
this->next();
return inner_expr;
}
if (UDQ::scalarFunc(current.type) || UDQ::elementalUnaryFunc(current.type)) {
auto func_node = current;
auto next = this->next();
if (next.type == UDQTokenType::open_paren) {
this->next();
auto arg_expr = this->parse_cmp();
current = this->current();
if (current.type != UDQTokenType::close_paren)
return UDQTokenType::error;
this->next();
return UDQASTNode(func_node.type, func_node.value, arg_expr);
} else
return UDQTokenType::error;
}
UDQASTNode node(current.type, current.value, current.selector);
this->next();
return node;
}
UDQASTNode UDQParser::parse_pow() {
auto left = this->parse_factor();
auto current = this->current();
if (current.type == UDQTokenType::end)
return left;
if (current.type == UDQTokenType::binary_op_pow) {
auto func_node = current;
this->next();
auto right = this->parse_mul();
if (right.type == UDQTokenType::end)
return UDQASTNode(UDQTokenType::error);
return UDQASTNode(current.type, current.value, left, right);
}
return left;
}
UDQASTNode UDQParser::parse_mul() {
auto left = this->parse_pow();
auto current = this->current();
if (current.type == UDQTokenType::end)
return left;
if (current.type == UDQTokenType::binary_op_mul || current.type == UDQTokenType::binary_op_div) {
auto func_node = current;
this->next();
auto right = this->parse_mul();
if (right.type == UDQTokenType::end)
return UDQASTNode(UDQTokenType::error);
return UDQASTNode(current.type, current.value, left, right);
}
return left;
}
UDQASTNode UDQParser::parse_add() {
auto left = this->parse_mul();
auto current = this->current();
if (current.type == UDQTokenType::end)
return left;
if (current.type == UDQTokenType::binary_op_add || current.type == UDQTokenType::binary_op_sub) {
auto func_node = current;
auto next = this->next();
auto right = this->parse_add();
if (right.type == UDQTokenType::end)
return UDQASTNode(UDQTokenType::error);
return UDQASTNode(current.type, current.value, left, right);
}
return left;
}
/*
A bit uncertain on the presedence of the comparison operators. In normal C the
comparion operators bind weaker than addition, i.e. for the assignment:
auto cmp = a + b < c;
The sum (a+b) is evaluated and then compared with c, that is the order of
presedence implemented here. But reading the eclipse UDQ manual one can get
the imporession that the relation operators should bind "very strong", i.e.
that (b < c) should be evaluated first, and then the result of the comparison
added to a.
*/
UDQASTNode UDQParser::parse_cmp() {
auto left = this->parse_add();
auto current = this->current();
if (current.type == UDQTokenType::end)
return left;
if (UDQ::cmpFunc(current.type)) {
auto func_node = current;
this->next();
auto right = this->parse_cmp();
if (right.type == UDQTokenType::end)
return UDQASTNode(UDQTokenType::error);
return UDQASTNode(current.type, current.value, left, right);
}
return left;
}
UDQASTNode UDQParser::parse(const UDQParams& udq_params, const std::vector<std::string>& tokens, const ParseContext& parseContext, ErrorGuard& errors)
{
UDQParser parser(udq_params, tokens);
parser.next();
auto tree = parser.parse_cmp();
auto current = parser.current();
if (current.type != UDQTokenType::end || tree.type == UDQTokenType::error) {
if (current.type != UDQTokenType::end) {
size_t index = parser.current_pos;
std::string msg = "Extra unhandled data starting with token[" + std::to_string(index) + "] = " + current.value;
parseContext.handleError(ParseContext::UDQ_PARSE_ERROR, msg, errors);
}
if (tree.type == UDQTokenType::error) {
std::string msg = "Failed to parse UDQ expression";
parseContext.handleError(ParseContext::UDQ_PARSE_ERROR, msg, errors);
}
return UDQASTNode( udq_params.undefinedValue() );
}
return tree;
}
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2019 Equinor ASA.
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 <http://www.gnu.org/licenses/>.
*/
#ifndef UDQPARSER_HPP
#define UDQPARSER_HPP
#include <string>
#include <vector>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQParams.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQEnums.hpp>
#include "UDQASTNode.hpp"
namespace Opm {
class ParseContext;
class ErrorGuard;
struct UDQParseNode {
UDQParseNode(UDQTokenType type_arg, const std::string& value_arg, const std::vector<std::string>& selector) :
type(type_arg),
value(value_arg),
selector(selector)
{}
UDQParseNode(UDQTokenType type_arg, const std::string& value_arg) :
UDQParseNode(type_arg, value_arg, {})
{}
// Implicit converting constructor.
UDQParseNode(UDQTokenType type_arg) : UDQParseNode(type_arg, "")
{}
UDQTokenType type;
std::string value;
std::vector<std::string> selector;
};
class UDQParser {
public:
static UDQASTNode parse(const UDQParams& udq_params, const std::vector<std::string>& tokens, const ParseContext& parseContext, ErrorGuard& errors);
private:
UDQParser(const UDQParams& udq_params, const std::vector<std::string>& tokens) :
udq_params(udq_params),
udqft(UDQFunctionTable(udq_params)),
tokens(tokens)
{}
UDQASTNode parse_cmp();
UDQASTNode parse_add();
UDQASTNode parse_factor();
UDQASTNode parse_mul();
UDQASTNode parse_pow();
UDQParseNode current() const;
UDQParseNode next();
UDQTokenType get_type(const std::string& arg) const;
std::size_t current_size() const;
const UDQParams& udq_params;
UDQFunctionTable udqft;
std::vector<std::string> tokens;
ssize_t current_pos = -1;
};
}
#endif

View File

@ -105,6 +105,8 @@ namespace Opm {
addKey(SIMULATOR_KEYWORD_NOT_SUPPORTED, InputError::WARN);
addKey(SIMULATOR_KEYWORD_ITEM_NOT_SUPPORTED, InputError::WARN);
addKey(UDQ_PARSE_ERROR, InputError::THROW_EXCEPTION);
}
void ParseContext::initEnv() {
@ -334,4 +336,6 @@ namespace Opm {
const std::string ParseContext::SIMULATOR_KEYWORD_NOT_SUPPORTED = "SIMULATOR_KEYWORD_NOT_SUPPORTED";
const std::string ParseContext::SIMULATOR_KEYWORD_ITEM_NOT_SUPPORTED = "SIMULATOR_KEYWORD_ITEM_NOT_SUPPORTED";
const std::string ParseContext::UDQ_PARSE_ERROR = "UDQ_PARSE_ERROR";
}

View File

@ -354,6 +354,8 @@ WUBHP
/
WUOPR
/
WUWCT
/
SCHEDULE
-- -------------------------------------------------------------------------
RPTSCHED
@ -370,6 +372,8 @@ UDQ
ASSIGN WUBHP P4 14 /
UNITS WUBHP 'BARSA' /
UNITS WUOPR 'SM3/DAY' /
DEFINE WUWCT WWPR / (WWPR + WOPR) /
UNITS WUWCT '1' /
/

530
tests/msim/actionx2.include Normal file
View File

@ -0,0 +1,530 @@
std::string actionx = R"(
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
---------------------------------------------------------------------------
------------------------ SPE1 - CASE 1 ------------------------------------
---------------------------------------------------------------------------
RUNSPEC
-- -------------------------------------------------------------------------
TITLE
SPE1 - CASE 1
DIMENS
10 10 3 /
-- The number of equilibration regions is inferred from the EQLDIMS
-- keyword.
EQLDIMS
/
-- The number of PVTW tables is inferred from the TABDIMS keyword;
-- when no data is included in the keyword the default values are used.
TABDIMS
/
OIL
GAS
WATER
DISGAS
-- As seen from figure 4 in Odeh, GOR is increasing with time,
-- which means that dissolved gas is present
FIELD
START
1 'DEC' 2014 /
WELLDIMS
-- Item 1: maximum number of wells in the model
-- - there are two wells in the problem; injector and producer
-- Item 2: maximum number of grid blocks connected to any one well
-- - must be one as the wells are located at specific grid blocks
-- Item 3: maximum number of groups in the model
-- - we are dealing with only one 'group'
-- Item 4: maximum number of wells in any one group
-- - there must be two wells in a group as there are two wells in total
2 1 1 2 /
UNIFOUT
UDQDIMS
50 25 0 50 50 0 0 50 0 20 /
GRID
-- The INIT keyword is used to request an .INIT file. The .INIT file
-- is written before the simulation actually starts, and contains grid
-- properties and saturation tables as inferred from the input
-- deck. There are no other keywords which can be used to configure
-- exactly what is written to the .INIT file.
INIT
-- -------------------------------------------------------------------------
NOECHO
DX
-- There are in total 300 cells with length 1000ft in x-direction
300*1000 /
DY
-- There are in total 300 cells with length 1000ft in y-direction
300*1000 /
DZ
-- The layers are 20, 30 and 50 ft thick, in each layer there are 100 cells
100*20 100*30 100*50 /
TOPS
-- The depth of the top of each grid block
100*8325 /
PORO
-- Constant porosity of 0.3 throughout all 300 grid cells
300*0.3 /
PERMX
-- The layers have perm. 500mD, 50mD and 200mD, respectively.
100*500 100*50 100*200 /
PERMY
-- Equal to PERMX
100*500 100*50 100*200 /
PERMZ
-- Cannot find perm. in z-direction in Odeh's paper
-- For the time being, we will assume PERMZ equal to PERMX and PERMY:
100*500 100*50 100*200 /
ECHO
PROPS
-- -------------------------------------------------------------------------
PVTW
-- Item 1: pressure reference (psia)
-- Item 2: water FVF (rb per bbl or rb per stb)
-- Item 3: water compressibility (psi^{-1})
-- Item 4: water viscosity (cp)
-- Item 5: water 'viscosibility' (psi^{-1})
-- Using values from Norne:
-- In METRIC units:
-- 277.0 1.038 4.67E-5 0.318 0.0 /
-- In FIELD units:
4017.55 1.038 3.22E-6 0.318 0.0 /
ROCK
-- Item 1: reference pressure (psia)
-- Item 2: rock compressibility (psi^{-1})
-- Using values from table 1 in Odeh:
14.7 3E-6 /
SWOF
-- Column 1: water saturation
-- - this has been set to (almost) equally spaced values from 0.12 to 1
-- Column 2: water relative permeability
-- - generated from the Corey-type approx. formula
-- the coeffisient is set to 10e-5, S_{orw}=0 and S_{wi}=0.12
-- Column 3: oil relative permeability when only oil and water are present
-- - we will use the same values as in column 3 in SGOF.
-- This is not really correct, but since only the first
-- two values are of importance, this does not really matter
-- Column 4: water-oil capillary pressure (psi)
0.12 0 1 0
0.18 4.64876033057851E-008 1 0
0.24 0.000000186 0.997 0
0.3 4.18388429752066E-007 0.98 0
0.36 7.43801652892562E-007 0.7 0
0.42 1.16219008264463E-006 0.35 0
0.48 1.67355371900826E-006 0.2 0
0.54 2.27789256198347E-006 0.09 0
0.6 2.97520661157025E-006 0.021 0
0.66 3.7654958677686E-006 0.01 0
0.72 4.64876033057851E-006 0.001 0
0.78 0.000005625 0.0001 0
0.84 6.69421487603306E-006 0 0
0.91 8.05914256198347E-006 0 0
1 0.00001 0 0 /
SGOF
-- Column 1: gas saturation
-- Column 2: gas relative permeability
-- Column 3: oil relative permeability when oil, gas and connate water are present
-- Column 4: oil-gas capillary pressure (psi)
-- - stated to be zero in Odeh's paper
-- Values in column 1-3 are taken from table 3 in Odeh's paper:
0 0 1 0
0.001 0 1 0
0.02 0 0.997 0
0.05 0.005 0.980 0
0.12 0.025 0.700 0
0.2 0.075 0.350 0
0.25 0.125 0.200 0
0.3 0.190 0.090 0
0.4 0.410 0.021 0
0.45 0.60 0.010 0
0.5 0.72 0.001 0
0.6 0.87 0.0001 0
0.7 0.94 0.000 0
0.85 0.98 0.000 0
0.88 0.984 0.000 0 /
--1.00 1.0 0.000 0 /
-- Warning from Eclipse: first sat. value in SWOF + last sat. value in SGOF
-- must not be greater than 1, but Eclipse still runs
-- Flow needs the sum to be excactly 1 so I added a row with gas sat. = 0.88
-- The corresponding krg value was estimated by assuming linear rel. between
-- gas sat. and krw. between gas sat. 0.85 and 1.00 (the last two values given)
DENSITY
-- Density (lb per ft³) at surface cond. of
-- oil, water and gas, respectively (in that order)
-- Using values from Norne:
-- In METRIC units:
-- 859.5 1033.0 0.854 /
-- In FIELD units:
53.66 64.49 0.0533 /
PVDG
-- Column 1: gas phase pressure (psia)
-- Column 2: gas formation volume factor (rb per Mscf)
-- - in Odeh's paper the units are said to be given in rb per bbl,
-- but this is assumed to be a mistake: FVF-values in Odeh's paper
-- are given in rb per scf, not rb per bbl. This will be in
-- agreement with conventions
-- Column 3: gas viscosity (cP)
-- Using values from lower right table in Odeh's table 2:
14.700 166.666 0.008000
264.70 12.0930 0.009600
514.70 6.27400 0.011200
1014.7 3.19700 0.014000
2014.7 1.61400 0.018900
2514.7 1.29400 0.020800
3014.7 1.08000 0.022800
4014.7 0.81100 0.026800
5014.7 0.64900 0.030900
9014.7 0.38600 0.047000 /
PVTO
-- Column 1: dissolved gas-oil ratio (Mscf per stb)
-- Column 2: bubble point pressure (psia)
-- Column 3: oil FVF for saturated oil (rb per stb)
-- Column 4: oil viscosity for saturated oil (cP)
-- Use values from top left table in Odeh's table 2:
0.0010 14.7 1.0620 1.0400 /
0.0905 264.7 1.1500 0.9750 /
0.1800 514.7 1.2070 0.9100 /
0.3710 1014.7 1.2950 0.8300 /
0.6360 2014.7 1.4350 0.6950 /
0.7750 2514.7 1.5000 0.6410 /
0.9300 3014.7 1.5650 0.5940 /
1.2700 4014.7 1.6950 0.5100
9014.7 1.5790 0.7400 /
1.6180 5014.7 1.8270 0.4490
9014.7 1.7370 0.6310 /
-- It is required to enter data for undersaturated oil for the highest GOR
-- (i.e. the last row) in the PVTO table.
-- In order to fulfill this requirement, values for oil FVF and viscosity
-- at 9014.7psia and GOR=1.618 for undersaturated oil have been approximated:
-- It has been assumed that there is a linear relation between the GOR
-- and the FVF when keeping the pressure constant at 9014.7psia.
-- From Odeh we know that (at 9014.7psia) the FVF is 2.357 at GOR=2.984
-- for saturated oil and that the FVF is 1.579 at GOR=1.27 for undersaturated oil,
-- so it is possible to use the assumption described above.
-- An equivalent approximation for the viscosity has been used.
/
SOLUTION
-- -------------------------------------------------------------------------
EQUIL
-- Item 1: datum depth (ft)
-- Item 2: pressure at datum depth (psia)
-- - Odeh's table 1 says that initial reservoir pressure is
-- 4800 psi at 8400ft, which explains choice of item 1 and 2
-- Item 3: depth of water-oil contact (ft)
-- - chosen to be directly under the reservoir
-- Item 4: oil-water capillary pressure at the water oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 5: depth of gas-oil contact (ft)
-- - chosen to be directly above the reservoir
-- Item 6: gas-oil capillary pressure at gas-oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 7: RSVD-table
-- Item 8: RVVD-table
-- Item 9: Set to 0 as this is the only value supported by OPM
-- Item #: 1 2 3 4 5 6 7 8 9
8400 4800 8450 0 8300 0 1 0 0 /
RSVD
-- Dissolved GOR is initially constant with depth through the reservoir.
-- The reason is that the initial reservoir pressure given is higher
---than the bubble point presssure of 4014.7psia, meaning that there is no
-- free gas initially present.
8300 1.270
8450 1.270 /
SUMMARY
-- -------------------------------------------------------------------------
FOPR
WGOR
/
WOPR
/
WWPR
/
WWCT
/
FGOR
-- 2a) Pressures of the cell where the injector and producer are located
BPR
1 1 1 /
10 10 3 /
/
-- 2b) Gas saturation at grid points given in Odeh's paper
BGSAT
1 1 1 /
1 1 2 /
1 1 3 /
10 1 1 /
10 1 2 /
10 1 3 /
10 10 1 /
10 10 2 /
10 10 3 /
/
-- In order to compare Eclipse with Flow:
WBHP
/
WGIR
'INJ'
/
WGIT
'INJ'
/
WGPR
/
WGPT
/
WOPR
/
WOPT
/
WWIR
/
WWIT
/
WWPR
/
WWPT
/
SCHEDULE
-- -------------------------------------------------------------------------
RPTSCHED
'PRES' 'SGAS' 'RS' 'WELLS' 'WELSPECS' /
RPTRST
'BASIC=1' /
UDQ
DEFINE WUPR3 SORTA(WOPR 'P*') /
/
-- If no resolution (i.e. case 1), the two following lines must be added:
DRSDT
0 /
-- if DRSDT is set to 0, GOR cannot rise and free gas does not
-- dissolve in undersaturated oil -> constant bubble point pressure
WELSPECS
-- Item #: 1 2 3 4 5 6
'P1' 'G1' 3 3 8400 'OIL' /
'P2' 'G1' 4 4 8400 'OIL' /
'P3' 'G1' 5 5 8400 'OIL' /
'P4' 'G1' 6 6 8400 'OIL' /
'INJ' 'G1' 1 1 8335 'GAS' /
/
-- Coordinates in item 3-4 are retrieved from Odeh's figure 1 and 2
-- Note that the depth at the midpoint of the well grid blocks
-- has been used as reference depth for bottom hole pressure in item 5
COMPDAT
-- Item #: 1 2 3 4 5 6 7 8 9
'P1' 3 3 3 3 'OPEN' 1* 1* 0.5 /
'P2' 4 4 3 3 'OPEN' 1* 1* 0.5 /
'P3' 5 5 3 3 'OPEN' 1* 1* 0.5 /
'P4' 6 6 3 3 'OPEN' 1* 1* 0.5 /
'INJ' 1 1 1 1 'OPEN' 1* 1* 0.5 /
/
-- Coordinates in item 2-5 are retreived from Odeh's figure 1 and 2
-- Item 9 is the well bore internal diameter,
-- the radius is given to be 0.25ft in Odeh's paper
WCONPROD
-- Item #:1 2 3 4 5 9
'P1' 'OPEN' 'ORAT' 5000 4* 1000 /
'P2' 'OPEN' 'ORAT' 5000 4* 1000 /
'P3' 'OPEN' 'ORAT' 5000 4* 1000 /
'P4' 'OPEN' 'ORAT' 5000 4* 1000 /
/
-- It is stated in Odeh's paper that the maximum oil prod. rate
-- is 20 000stb per day which explains the choice of value in item 4.
-- The items > 4 are defaulted with the exception of item 9,
-- the BHP lower limit, which is given to be 1000psia in Odeh's paper
WCONINJE
-- Item #:1 2 3 4 5 6 7
'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
/
-- Stated in Odeh that gas inj. rate (item 5) is 100MMscf per day
-- BHP upper limit (item 7) should not be exceeding the highest
-- pressure in the PVT table=9014.7psia (default is 100 000psia)
DATES
1 'JAN' 2015 /
/
DATES
1 'FEB' 2015 /
/
DATES
1 'MAR' 2015 /
/
DATES
1 'APR' 2015 /
/
DATES
1 'MAI' 2015 /
/
DATES
1 'JUN' 2015 /
/
DATES
1 'JUL' 2015 /
/
DATES
1 'AUG' 2015 /
/
DATES
1 'SEP' 2015 /
/
DATES
1 'OCT' 2015 /
/
DATES
1 'NOV' 2015 /
/
DATES
1 'DEC' 2015 /
/
DATES
1 'JAN' 2016 /
/
DATES
1 'FEB' 2016 /
/
DATES
1 'MAR' 2016 /
/
DATES
1 'APR' 2016 /
/
DATES
1 'MAI' 2016 /
/
DATES
1 'JUN' 2016 /
/
ACTIONX
'SHUT_WELL' 100000 /
WUPR3 'P*' = 1 /
/
WELOPEN
'?' 'SHUT' /
/
ENDACTIO
DATES
1 'JUL' 2016 /
/
DATES
1 'AUG' 2016 /
/
DATES
1 'SEP' 2016 /
/
DATES
1 'OCT' 2016 /
/
DATES
1 'NOV' 2016 /
/
DATES
1 'DEC' 2016 /
/
END
)";

View File

@ -72,6 +72,12 @@ double prod_opr(const EclipseState& es, const Schedule& sched, const data::Solu
return -units.to_si(UnitSystem::measure::rate, oil_rate);
}
double prod_opr_low(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) {
const auto& units = es.getUnits();
double oil_rate = 0.5;
return -units.to_si(UnitSystem::measure::rate, oil_rate);
}
double prod_wpr_P1(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) {
const auto& units = es.getUnits();
double water_rate = 0.0;
@ -102,6 +108,42 @@ double prod_wpr_P4(const EclipseState& es, const Schedule& sched, const data::S
return -units.to_si(UnitSystem::measure::rate, water_rate);
}
/*
The deck tested here has a UDQ DEFINE statement which sorts the wells after
oil production rate, and then subsequently closes the well with lowest OPR
with a ACTIONX keyword.
*/
BOOST_AUTO_TEST_CASE(UDQ_SORTA_EXAMPLE) {
#include "actionx2.include"
test_data td( actionx );
msim sim(td.state);
{
test_work_area_type * work_area = test_work_area_alloc("test_msim");
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
sim.well_rate("P2", data::Rates::opt::oil, prod_opr);
sim.well_rate("P3", data::Rates::opt::oil, prod_opr);
sim.well_rate("P4", data::Rates::opt::oil, prod_opr_low);
sim.run(td.schedule, io);
{
const auto& w1 = td.schedule.getWell("P1");
const auto& w4 = td.schedule.getWell("P4");
std::size_t last_step = td.schedule.size() - 1;
BOOST_CHECK_EQUAL(w1->getStatus(1), WellCommon::StatusEnum::OPEN );
BOOST_CHECK_EQUAL(w4->getStatus(1), WellCommon::StatusEnum::OPEN );
BOOST_CHECK_EQUAL(w1->getStatus(last_step), WellCommon::StatusEnum::OPEN );
BOOST_CHECK_EQUAL(w4->getStatus(last_step), WellCommon::StatusEnum::SHUT );
}
test_work_area_free(work_area);
}
}
BOOST_AUTO_TEST_CASE(WELL_CLOSE_EXAMPLE) {
#include "actionx1.include"
@ -204,3 +246,43 @@ BOOST_AUTO_TEST_CASE(UDQ_ASSIGN) {
test_work_area_free(work_area);
}
}
BOOST_AUTO_TEST_CASE(UDQ_WUWCT) {
#include "actionx1.include"
test_data td( actionx1 );
msim sim(td.state);
{
test_work_area_type * work_area = test_work_area_alloc("test_msim");
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
sim.well_rate("P2", data::Rates::opt::oil, prod_opr);
sim.well_rate("P3", data::Rates::opt::oil, prod_opr);
sim.well_rate("P4", data::Rates::opt::oil, prod_opr_low);
sim.well_rate("P1", data::Rates::opt::wat, prod_wpr_P1);
sim.well_rate("P2", data::Rates::opt::wat, prod_wpr_P2);
sim.well_rate("P3", data::Rates::opt::wat, prod_wpr_P3);
sim.well_rate("P4", data::Rates::opt::wat, prod_wpr_P4);
sim.run(td.schedule, io);
const auto& base_name = td.state.getIOConfig().getBaseName();
ecl_sum_type * ecl_sum = ecl_sum_fread_alloc_case( base_name.c_str(), ":");
for (const auto& well : {"P1", "P2", "P3", "P4"}) {
std::string wwct_key = std::string("WWCT:") + well;
std::string wuwct_key = std::string("WUWCT:") + well;
BOOST_CHECK( ecl_sum_has_general_var(ecl_sum, wwct_key.c_str()));
BOOST_CHECK( ecl_sum_has_general_var(ecl_sum, wuwct_key.c_str()));
for (int step = 0; step < ecl_sum_get_data_length(ecl_sum); step++)
BOOST_CHECK_EQUAL( ecl_sum_get_general_var(ecl_sum, step, wwct_key.c_str()),
ecl_sum_get_general_var(ecl_sum, step, wuwct_key.c_str()));
}
test_work_area_free(work_area);
}
}

View File

@ -28,7 +28,6 @@
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQInput.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQWellSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQExpression.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQContext.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQAssign.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunction.hpp>
@ -49,6 +48,28 @@ Schedule make_schedule(const std::string& input) {
return Schedule(deck, grid , eclipseProperties, runspec);
}
BOOST_AUTO_TEST_CASE(MIX_SCALAR) {
UDQFunctionTable udqft;
UDQParams udqp;
UDQDefine def_add(udqp, "WU", {"WOPR", "+", "1"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WOPR", 1);
auto res_add = def_add.eval_wells(context);
BOOST_CHECK_EQUAL( res_add["P1"].value() , 2);
}
BOOST_AUTO_TEST_CASE(UDQ_TABLE_EXCEPTION) {
UDQParams udqp;
BOOST_CHECK_THROW(UDQDefine(udqp, "WU", {"TUPRICE[WOPR]"}), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(UDQWellSetTest) {
std::vector<std::string> wells = {"P1", "P2", "I1", "I2"};
UDQWellSet ws("NAME", wells);
@ -91,6 +112,85 @@ BOOST_AUTO_TEST_CASE(UDQWellSetTest) {
BOOST_CHECK_EQUAL(ws3[wells[i]].value(), i*1.0);
}
BOOST_AUTO_TEST_CASE(UDQ_DEFINETEST) {
UDQParams udqp;
UDQFunctionTable udqft(udqp);
{
UDQDefine def(udqp, "WUBHP", {"WBHP"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("W1", "WBHP", 11);
st.add_well_var("W2", "WBHP", 2);
st.add_well_var("W3", "WBHP", 3);
auto res = def.eval_wells(context);
BOOST_CHECK_EQUAL(res.size(), 3);
BOOST_CHECK_EQUAL( res["W1"].value(), 11 );
BOOST_CHECK_EQUAL( res["W2"].value(), 2 );
BOOST_CHECK_EQUAL( res["W3"].value(), 3 );
}
{
UDQDefine def(udqp, "WUBHP", {"WBHP" , "'P*'"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WBHP", 1);
st.add_well_var("P2", "WBHP", 2);
st.add_well_var("I1", "WBHP", 1);
st.add_well_var("I2", "WBHP", 2);
auto res = def.eval_wells(context);
BOOST_CHECK_EQUAL(res.size(), 4);
BOOST_CHECK_EQUAL( res["P1"].value(), 1 );
BOOST_CHECK_EQUAL( res["P2"].value(), 2 );
BOOST_CHECK_EQUAL( res["I1"].defined(), false);
BOOST_CHECK_EQUAL( res["I1"].defined(), false);
}
{
UDQDefine def(udqp, "WUBHP", {"WBHP" , "'P1'"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WBHP", 1);
BOOST_CHECK_THROW( def.eval_wells( context ), std::invalid_argument);
}
{
UDQDefine def(udqp, "WUBHP", {"NINT" , "(", "WBHP", ")"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WBHP", 4);
st.add_well_var("P2", "WBHP", 3);
st.add_well_var("I1", "WBHP", 2);
st.add_well_var("I2", "WBHP", 1);
auto res = def.eval_wells(context);
BOOST_CHECK_EQUAL( res["P1"].value(), 4 );
BOOST_CHECK_EQUAL( res["P2"].value(), 3 );
BOOST_CHECK_EQUAL( res["I1"].value(), 2 );
BOOST_CHECK_EQUAL( res["I2"].value(), 1 );
}
// This should eventually fail because the SUM() function evaluates to
// scalar context and that is not appropriate for the WUBHP variable which
// should evaluate to a full well set.
{
UDQDefine def(udqp, "WUBHP", {"SUM" , "(", "WBHP", ")"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WBHP", 1);
BOOST_CHECK_THROW( def.eval_wells( context ), std::invalid_argument);
}
}
BOOST_AUTO_TEST_CASE(KEYWORDS) {
const std::string input = R"(
RUNSPEC
@ -133,6 +233,11 @@ BOOST_AUTO_TEST_CASE(ENUM_CONVERSION) {
BOOST_CHECK(UDQ::varType("RUBHP") == UDQVarType::REGION_VAR);
BOOST_CHECK(UDQ::varType("AUBHP") == UDQVarType::AQUIFER_VAR);
BOOST_CHECK(UDQ::varType("SUBHP") == UDQVarType::SEGMENT_VAR);
BOOST_REQUIRE_THROW( UDQ::actionType("INVALID_ACTION"), std::invalid_argument);
BOOST_CHECK(UDQ::actionType("DEFINE") == UDQAction::DEFINE );
BOOST_CHECK(UDQ::actionType("UNITS") == UDQAction::UNITS );
BOOST_CHECK(UDQ::actionType("ASSIGN") == UDQAction::ASSIGN );
}
@ -154,6 +259,7 @@ UDQ
UNITS WUBHP 'BARSA' /
DEFINE FUOPR AVEG(WOPR) + 1/
ASSIGN WUXUNIT 0.0 /
DEFINE FUOPR AVEG(WOPR)/
/
DATES
@ -162,14 +268,14 @@ DATES
UDQ
ASSIGN WUBHP 0.0 /
DEFINE FUOPR AVEG(WOPR) + 1/
DEFINE FUOPR AVEG(WOPR)/
UNITS WUBHP 'BARSA' / -- Repeating the same unit multiple times is superfluous but OK
/
)";
auto schedule = make_schedule(input);
const auto& udq = schedule.getUDQConfig(0);
BOOST_CHECK_EQUAL(1, udq.expressions().size());
BOOST_CHECK_EQUAL(2, udq.assignments().size());
BOOST_CHECK_THROW( udq.unit("NO_SUCH_KEY"), std::invalid_argument );
BOOST_CHECK_EQUAL( udq.unit("WUBHP"), "BARSA");
@ -233,49 +339,6 @@ UDQ
BOOST_AUTO_TEST_CASE(UDQ_KEYWORD) {
// Invalid action
BOOST_REQUIRE_THROW( UDQ::actionType("INVALID_ACTION"), std::invalid_argument);
// Invalid keyword
BOOST_REQUIRE_THROW( UDQExpression(UDQAction::ASSIGN, "INVALID_KEYWORD", {}), std::invalid_argument);
BOOST_CHECK_NO_THROW(UDQExpression(UDQAction::ASSIGN ,"WUBHP", {"1"}));
}
BOOST_AUTO_TEST_CASE(UDQ_DEFINE_DATA) {
const std::string input = R"(
RUNSPEC
UDQDIMS
10* 'Y'/
UDQPARAM
3* 0.25 /
SCHEDULE
UDQ
DEFINE CUMW1 P12 10 12 1 (4.0 + 6*(4 - 2)) /
DEFINE WUMW1 WBHP 'P*1*' UMAX WBHP 'P*4*' /
/
)";
const auto schedule = make_schedule(input);
const auto& udq = schedule.getUDQConfig(0);
const auto& records = udq.expressions();
const auto& rec0 = records[0];
const auto& rec1 = records[1];
const std::vector<std::string> exp0 = {"P12", "10", "12", "1", "(", "4.0", "+", "6", "*", "(", "4", "-", "2", ")", ")"};
const std::vector<std::string> exp1 = {"WBHP", "P*1*", "UMAX", "WBHP" , "P*4*"};
BOOST_CHECK_EQUAL_COLLECTIONS(rec0.tokens().begin(), rec0.tokens().end(), exp0.begin(), exp0.end());
BOOST_CHECK_EQUAL_COLLECTIONS(rec1.tokens().begin(), rec1.tokens().end(), exp1.begin(), exp1.end());
}
BOOST_AUTO_TEST_CASE(UDQ_DEFINE_WITH_SLASH) {
const std::string input = R"(
UDQ
@ -339,7 +402,8 @@ ASSIGN WU2 8.0 /
BOOST_AUTO_TEST_CASE(UDQ_CONTEXT) {
SummaryState st;
UDQContext ctx(st);
UDQFunctionTable func_table;
UDQContext ctx(func_table, st);
BOOST_CHECK_EQUAL(ctx.get("JAN"), 1.0);
BOOST_REQUIRE_THROW(ctx.get("NO_SUCH_KEY"), std::out_of_range);
@ -407,8 +471,7 @@ BOOST_AUTO_TEST_CASE(UDQ_SET) {
BOOST_AUTO_TEST_CASE(UDQ_FUNCTION_TABLE) {
UDQParams params;
UDQFunctionTable udqft(params);
UDQFunctionTable udqft;
BOOST_CHECK(udqft.has_function("SUM"));
BOOST_CHECK(!udqft.has_function("NO_SUCH_FUNCTION"));
UDQSet arg("NAME", 5);
@ -472,8 +535,7 @@ BOOST_AUTO_TEST_CASE(UDQ_FUNCTION_TABLE) {
}
BOOST_AUTO_TEST_CASE(CMP_FUNCTIONS) {
UDQParams params;
UDQFunctionTable udqft(params);
UDQFunctionTable udqft;
UDQSet arg1("NAME", 5);
UDQSet arg2("NAME", 5);
UDQSet arg3("NAME", 3);
@ -559,16 +621,14 @@ BOOST_AUTO_TEST_CASE(CMP_FUNCTIONS) {
}
BOOST_AUTO_TEST_CASE(BAD_CAST) {
UDQParams params;
UDQFunctionTable udqft(params);
UDQFunctionTable udqft;
BOOST_CHECK_THROW( dynamic_cast<const UDQUnaryElementalFunction&>(udqft.get("==")), std::bad_cast);
}
BOOST_AUTO_TEST_CASE(ELEMENTAL_UNARY_FUNCTIONS) {
UDQParams params;
UDQFunctionTable udqft(params);
UDQFunctionTable udqft;
UDQSet arg("NAME", 5);
arg.assign(0,1);
arg.assign(2,2);
@ -656,28 +716,27 @@ BOOST_AUTO_TEST_CASE(ELEMENTAL_UNARY_FUNCTIONS) {
const auto& func = dynamic_cast<const UDQUnaryElementalFunction&>(udqft.get("SORTA"));
auto result = func.eval(arg);
BOOST_CHECK_EQUAL( result[0].value(), 0);
BOOST_CHECK_EQUAL( result[0].value(), 1);
BOOST_CHECK( !result[1] );
BOOST_CHECK_EQUAL( result[2].value(), 1);
BOOST_CHECK_EQUAL( result[2].value(), 2);
BOOST_CHECK( !result[3] );
BOOST_CHECK_EQUAL( result[4].value(), 2);
BOOST_CHECK_EQUAL( result[4].value(), 3);
}
{
const auto& func = dynamic_cast<const UDQUnaryElementalFunction&>(udqft.get("SORTD"));
auto result = func.eval(arg);
BOOST_CHECK_EQUAL( result[0].value(), 2);
BOOST_CHECK_EQUAL( result[0].value(), 3);
BOOST_CHECK( !result[1] );
BOOST_CHECK_EQUAL( result[2].value(), 1);
BOOST_CHECK_EQUAL( result[2].value(), 2);
BOOST_CHECK( !result[3] );
BOOST_CHECK_EQUAL( result[4].value(), 0);
BOOST_CHECK_EQUAL( result[4].value(), 1);
}
}
BOOST_AUTO_TEST_CASE(UNION_FUNCTIONS) {
UDQParams params;
UDQFunctionTable udqft(params);
UDQFunctionTable udqft;
UDQSet arg1("NAME", 5);
UDQSet arg2("NAME", 5);
@ -721,7 +780,6 @@ BOOST_AUTO_TEST_CASE(UDQ_SET_DIV) {
BOOST_AUTO_TEST_CASE(UDQASSIGN_TEST) {
UDQAssign as1("WUPR", {}, 1.0);
UDQAssign as2("WUPR", {"P*"}, 2.0);
@ -745,3 +803,154 @@ BOOST_AUTO_TEST_CASE(UDQASSIGN_TEST) {
BOOST_CHECK(!res3["I1"].defined());
BOOST_CHECK(!res3["I2"].defined());
}
BOOST_AUTO_TEST_CASE(UDQ_POW_TEST) {
UDQFunctionTable udqft;
UDQParams udqp;
UDQDefine def_pow1(udqp, "WU", {"WOPR", "+", "WWPR", "*", "WGOR", "^", "WWIR"});
UDQDefine def_pow2(udqp, "WU", {"(", "WOPR", "+", "WWPR", ")", "^", "(", "WOPR", "+" , "WGOR", "*", "WWIR", "-", "WOPT", ")"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WOPR", 1);
st.add_well_var("P1", "WWPR", 2);
st.add_well_var("P1", "WGOR", 3);
st.add_well_var("P1", "WWIR", 4);
st.add_well_var("P1", "WOPT", 7);
auto res_pow1 = def_pow1.eval_wells(context);
auto res_pow2 = def_pow2.eval_wells(context);
BOOST_CHECK_EQUAL( res_pow1["P1"].value() , 1 + 2 * std::pow(3,4));
BOOST_CHECK_EQUAL( res_pow2["P1"].value() , std::pow(1 + 2, 1 + 3*4 - 7));
}
BOOST_AUTO_TEST_CASE(UDQ_CMP_TEST) {
UDQFunctionTable udqft;
UDQParams udqp;
UDQDefine def_cmp(udqp, "WU", {"WOPR", ">", "WWPR", "+", "WGOR", "*", "WWIR"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WOPR", 0);
st.add_well_var("P1", "WWPR", 10);
st.add_well_var("P1", "WGOR", -3);
st.add_well_var("P1", "WWIR", 4);
st.add_well_var("P2", "WOPR", 0);
st.add_well_var("P2", "WWPR", -2);
st.add_well_var("P2", "WGOR", 4);
st.add_well_var("P2", "WWIR", 1);
auto res_cmp = def_cmp.eval_wells(context);
BOOST_CHECK_EQUAL( res_cmp["P1"].value() , 1.0);
BOOST_CHECK_EQUAL( res_cmp["P2"].value() , 0.0);
}
/*BOOST_AUTO_TEST_CASE(UDQPARSE_ERROR) {
setUDQFunctionTable udqft;
UDQDefine def1(udqft, "WUBHP", {"WWCT", "+"});
}
*/
BOOST_AUTO_TEST_CASE(UDQ_BASIC_MATH_TEST) {
UDQParams udqp;
UDQFunctionTable udqft;
UDQDefine def_add(udqp, "WU2OPR", {"WOPR", "+", "WOPR"});
UDQDefine def_sub(udqp, "WU2OPR", {"WOPR", "-", "WOPR"});
UDQDefine def_mul(udqp, "WU2OPR", {"WOPR", "*", "WOPR"});
UDQDefine def_div(udqp, "WU2OPR", {"WOPR", "/", "WOPR"});
UDQDefine def_muladd(udqp , "WUX", {"WOPR", "+", "WOPR", "*", "WOPR"});
UDQDefine def_wuwct(udqp , "WUWCT", {"WWPR", "/", "(", "WOPR", "+", "WWPR", ")"});
SummaryState st;
UDQContext context(udqft, st);
st.add_well_var("P1", "WOPR", 1);
st.add_well_var("P2", "WOPR", 2);
st.add_well_var("P3", "WOPR", 3);
st.add_well_var("P4", "WOPR", 4);
st.add_well_var("P1", "WWPR", 1);
st.add_well_var("P2", "WWPR", 2);
st.add_well_var("P3", "WWPR", 3);
st.add_well_var("P4", "WWPR", 4);
auto res_add = def_add.eval_wells(context);
BOOST_CHECK_EQUAL( res_add.size(), 4);
BOOST_CHECK_EQUAL( res_add["P1"].value(), 2);
BOOST_CHECK_EQUAL( res_add["P2"].value(), 4);
BOOST_CHECK_EQUAL( res_add["P3"].value(), 6);
BOOST_CHECK_EQUAL( res_add["P4"].value(), 8);
auto res_sub = def_sub.eval_wells(context);
BOOST_CHECK_EQUAL( res_sub.size(), 4);
BOOST_CHECK_EQUAL( res_sub["P1"].value(), 0);
BOOST_CHECK_EQUAL( res_sub["P2"].value(), 0);
BOOST_CHECK_EQUAL( res_sub["P3"].value(), 0);
BOOST_CHECK_EQUAL( res_sub["P4"].value(), 0);
auto res_div = def_div.eval_wells(context);
BOOST_CHECK_EQUAL( res_div.size(), 4);
BOOST_CHECK_EQUAL( res_div["P1"].value(), 1);
BOOST_CHECK_EQUAL( res_div["P2"].value(), 1);
BOOST_CHECK_EQUAL( res_div["P3"].value(), 1);
BOOST_CHECK_EQUAL( res_div["P4"].value(), 1);
auto res_mul = def_mul.eval_wells(context);
BOOST_CHECK_EQUAL( res_mul.size(), 4);
BOOST_CHECK_EQUAL( res_mul["P1"].value(), 1);
BOOST_CHECK_EQUAL( res_mul["P2"].value(), 4);
BOOST_CHECK_EQUAL( res_mul["P3"].value(), 9);
BOOST_CHECK_EQUAL( res_mul["P4"].value(),16);
auto res_muladd = def_muladd.eval_wells(context);
BOOST_CHECK_EQUAL( res_muladd.size(), 4);
BOOST_CHECK_EQUAL( res_muladd["P1"].value(), 1 + 1);
BOOST_CHECK_EQUAL( res_muladd["P2"].value(), 4 + 2);
BOOST_CHECK_EQUAL( res_muladd["P3"].value(), 9 + 3);
BOOST_CHECK_EQUAL( res_muladd["P4"].value(),16 + 4);
auto res_wuwct= def_wuwct.eval_wells(context);
BOOST_CHECK_EQUAL( res_wuwct.size(), 4);
BOOST_CHECK_EQUAL( res_wuwct["P1"].value(),0.50);
BOOST_CHECK_EQUAL( res_wuwct["P2"].value(),0.50);
BOOST_CHECK_EQUAL( res_wuwct["P3"].value(),0.50);
BOOST_CHECK_EQUAL( res_wuwct["P4"].value(),0.50);
}
BOOST_AUTO_TEST_CASE(UDQPARSE_TEST1) {
UDQParams udqp;
UDQDefine def1(udqp, "WUBHP", {"1/(WWCT", "'W1*')"});
std::vector<std::string> tokens1 = {"1", "/", "(", "WWCT", "W1*", ")"};
BOOST_CHECK_EQUAL_COLLECTIONS(tokens1.begin(), tokens1.end(),
def1.tokens.begin(), def1.tokens.end());
UDQDefine def2(udqp, "WUBHP", {"2*(1", "+" , "WBHP)"});
std::vector<std::string> tokens2 = {"2", "*", "(", "1", "+", "WBHP", ")"};
BOOST_CHECK_EQUAL_COLLECTIONS(tokens2.begin(), tokens2.end(),
def2.tokens.begin(), def2.tokens.end());
}
BOOST_AUTO_TEST_CASE(UDQPARSE_PARSECONTEXT) {
UDQParams udqp;
ParseContext parseContext;
ErrorGuard errors;
std::vector<std::string> tokens = {"WBHP", "+"};
parseContext.update(ParseContext::UDQ_PARSE_ERROR, InputError::IGNORE);
{
UDQDefine def1(udqp, "WUBHP", tokens, parseContext, errors);
SummaryState st;
UDQContext context(UDQFunctionTable(udqp), st);
st.add_well_var("P1", "WOPR", 1);
printf("Have returned with def1 \n");
auto res = def1.eval_wells(context);
BOOST_CHECK_EQUAL(res["P1"].value(), udqp.undefinedValue());
}
parseContext.update(ParseContext::UDQ_PARSE_ERROR, InputError::THROW_EXCEPTION);
BOOST_CHECK_THROW( UDQDefine(udqp, "WUBHP", tokens, parseContext, errors), std::invalid_argument);
}