Add New Summary Node Category of Completion

This enables detecting the last remaining case that has a valid NUMS
entry despite nominally being a well-level keyword.
This commit is contained in:
Bård Skaflestad 2022-08-04 22:59:03 +02:00
parent c5158a1c24
commit 03a94a7288
7 changed files with 563 additions and 254 deletions

View File

@ -20,12 +20,11 @@
#ifndef OPM_IO_SUMMARYNODE_HPP
#define OPM_IO_SUMMARYNODE_HPP
#include <array>
#include <functional>
#include <limits>
#include <optional>
#include <string>
#include <unordered_set>
#include <array>
#include <limits>
namespace Opm { namespace EclIO {
@ -42,6 +41,7 @@ struct SummaryNode {
Region,
Block,
Connection,
Completion,
Segment,
Aquifer,
Node,
@ -59,7 +59,6 @@ struct SummaryNode {
Undefined,
};
std::string keyword;
Category category;
Type type;
@ -79,6 +78,14 @@ struct SummaryNode {
static Category category_from_keyword(const std::string&);
static std::string normalise_keyword(const Category category,
const std::string& keyword);
static inline std::string normalise_keyword(const std::string& keyword)
{
return normalise_keyword(category_from_keyword(keyword), keyword);
}
// Return true for keywords which should be Miscellaneous, although the
// naive first-character-based classification suggests something else.
static bool miscellaneous_exception(const std::string& keyword);

View File

@ -310,10 +310,6 @@ struct SummaryConfigContext {
&& is_in_set(countkw, keyword.substr(1));
}
bool is_liquid_phase(const std::string& keyword) {
return keyword == "WPIL";
}
bool is_supported_region_to_region(const std::string& keyword)
{
static const auto supported_kw = std::regex {
@ -363,39 +359,22 @@ struct SummaryConfigContext {
bool is_connection_completion(const std::string& keyword)
{
if (keyword[0] != 'C')
return false;
static const auto conn_compl_kw = std::regex {
R"(C[OGW][IP][RT]L)"
};
if (keyword.back() != 'L')
return false;
if (is_udq(keyword))
return false;
if (keyword.size() != 5)
return false;
return true;
return std::regex_match(keyword, conn_compl_kw);
}
bool is_well_completion(const std::string& keyword)
{
if (keyword[0] != 'W')
return false;
static const auto well_compl_kw = std::regex {
R"(W[OGWLV][PIGOLCF][RT]L([0-9_]{2}[0-9])?)"
};
if (keyword.back() != 'L')
return false;
if (is_liquid_phase(keyword))
return false;
if (is_udq(keyword))
return false;
if (keyword == "WMCTL")
return false;
return true;
// True, e.g., for WOPRL, WOPRL__8, WOPRL123, but not WOPRL___ or
// WKITL.
return std::regex_match(keyword, well_compl_kw);
}
bool is_node_keyword(const std::string& keyword)
@ -447,6 +426,21 @@ struct SummaryConfigContext {
: SummaryConfigNode::Category::Group;
}
SummaryConfigNode::Category
distinguish_connection_from_completion(const std::string& keyword)
{
return is_connection_completion(keyword)
? SummaryConfigNode::Category::Completion
: SummaryConfigNode::Category::Connection;
}
SummaryConfigNode::Category
distinguish_well_from_completion(const std::string& keyword)
{
return is_well_completion(keyword)
? SummaryConfigNode::Category::Completion
: SummaryConfigNode::Category::Well;
}
void handleMissingWell( const ParseContext& parseContext, ErrorGuard& errors, const KeywordLocation& location, const std::string& well) {
std::string msg_fmt = fmt::format("Request for missing well {} in {{keyword}}\n"
@ -561,34 +555,36 @@ inline std::array< int, 3 > getijk( const DeckRecord& record ) {
}
inline void keywordCL( SummaryConfig::keyword_list& list,
const ParseContext& parseContext,
ErrorGuard& errors,
const DeckKeyword& keyword,
const Schedule& schedule ,
const GridDims& dims)
inline void keywordCL(SummaryConfig::keyword_list& list,
const ParseContext& parseContext,
ErrorGuard& errors,
const DeckKeyword& keyword,
const Schedule& schedule,
const GridDims& dims)
{
auto node = SummaryConfigNode{keyword.name(), SummaryConfigNode::Category::Connection, keyword.location()};
node.parameterType( parseKeywordType(keyword.name()) );
node.isUserDefined( is_udq(keyword.name()) );
auto node = SummaryConfigNode {
keyword.name(), SummaryConfigNode::Category::Completion, keyword.location()
}
.parameterType(parseKeywordType(keyword.name()))
.isUserDefined(is_udq(keyword.name()));
for (const auto& record : keyword) {
const auto& pattern = record.getItem(0).get<std::string>(0);
auto well_names = schedule.wellNames( pattern, schedule.size() - 1 );
auto well_names = schedule.wellNames(pattern, schedule.size() - 1);
if (well_names.empty()) {
handleMissingWell(parseContext, errors, keyword.location(), pattern);
}
if( well_names.empty() )
handleMissingWell( parseContext, errors, keyword.location(), pattern );
const auto ijk_defaulted = record.getItem( 1 ).defaultApplied( 0 );
const auto ijk_defaulted = record.getItem(1).defaultApplied(0);
for (const auto& wname : well_names) {
const auto& well = schedule.getWellatEnd(wname);
const auto& all_connections = well.getConnections();
node.namedEntity( wname );
node.namedEntity(wname);
if (ijk_defaulted) {
for (const auto& conn : all_connections)
list.push_back( node.number( 1 + conn.global_index()));
list.push_back(node.number(1 + conn.global_index()));
} else {
const auto& ijk = getijk(record);
auto global_index = dims.getGlobalIndex(ijk[0], ijk[1], ijk[2]);
@ -599,7 +595,8 @@ inline void keywordCL( SummaryConfig::keyword_list& list,
} else {
std::string msg = fmt::format("Problem with keyword {{keyword}}\n"
"In {{file}} line {{line}}\n"
"Connection ({},{},{}) not defined for well {} ", ijk[0], ijk[1], ijk[2], wname);
"Connection ({},{},{}) not defined for well {}",
ijk[0] + 1, ijk[1] + 1, ijk[2] + 1, wname);
parseContext.handleError( ParseContext::SUMMARY_UNHANDLED_KEYWORD, msg, keyword.location(), errors);
}
}
@ -607,37 +604,46 @@ inline void keywordCL( SummaryConfig::keyword_list& list,
}
}
inline void keywordWL( SummaryConfig::keyword_list& list,
inline void keywordWL(SummaryConfig::keyword_list& list,
const ParseContext& parseContext,
ErrorGuard& errors,
const DeckKeyword& keyword,
const Schedule& schedule )
const Schedule& schedule)
{
for (const auto& record : keyword) {
const auto& pattern = record.getItem(0).get<std::string>(0);
const int completion = record.getItem(1).get<int>(0);
auto well_names = schedule.wellNames( pattern, schedule.size() - 1 );
const auto well_names = schedule.wellNames(pattern, schedule.size() - 1);
// We add the completion number both the extra field which contains
// parsed data from the keywordname - i.e. WOPRL__8 and also to the
// numeric member which will be written to the NUMS field.
auto node = SummaryConfigNode{ fmt::format("{}{:_>3}", keyword.name(), completion), SummaryConfigNode::Category::Well, keyword.location()};
node.parameterType( parseKeywordType(keyword.name()) );
node.isUserDefined( is_udq(keyword.name()) );
node.number(completion);
if (well_names.empty()) {
handleMissingWell(parseContext, errors, keyword.location(), pattern);
continue;
}
if( well_names.empty() )
handleMissingWell( parseContext, errors, keyword.location(), pattern );
const auto completion = record.getItem(1).get<int>(0);
// Use an amended KEYWORDS entry incorporating the completion ID,
// e.g. "WOPRL_12", for the W*L summary vectors. This is special
// case treatment for compatibility reasons as the more common entry
// here would be to just use "keyword.name()".
auto node = SummaryConfigNode {
fmt::format("{}{:_>3}", keyword.name(), completion),
SummaryConfigNode::Category::Completion, keyword.location()
}
.parameterType(parseKeywordType(keyword.name()))
.isUserDefined(is_udq(keyword.name()))
.number(completion);
for (const auto& wname : well_names) {
const auto& well = schedule.getWellatEnd(wname);
if (well.hasCompletion(completion))
list.push_back( node.namedEntity( wname ) );
if (schedule.getWellatEnd(wname).hasCompletion(completion)) {
list.push_back(node.namedEntity(wname));
}
else {
std::string msg = fmt::format("Problem with keyword {{keyword}}\n"
"In {{file}} line {{line}}\n"
"Completion number {} not defined for well {} ", completion, wname);
parseContext.handleError( ParseContext::SUMMARY_UNHANDLED_KEYWORD, msg, keyword.location(), errors);
const auto msg = fmt::format("Problem with keyword {{keyword}}\n"
"In {{file}} line {{line}}\n"
"Completion number {} not defined for well {}",
completion, wname);
parseContext.handleError(ParseContext::SUMMARY_UNHANDLED_KEYWORD,
msg, keyword.location(), errors);
}
}
}
@ -1170,7 +1176,7 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
void keywordSWithRecords(const std::size_t last_timestep,
const ParseContext& parseContext,
ErrorGuard& errors,
ErrorGuard& errors,
const DeckKeyword& keyword,
const Schedule& schedule,
SummaryConfig::keyword_list& list)
@ -1213,7 +1219,7 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
inline void keywordS(SummaryConfig::keyword_list& list,
const ParseContext& parseContext,
ErrorGuard& errors,
ErrorGuard& errors,
const DeckKeyword& keyword,
const Schedule& schedule)
{
@ -1253,18 +1259,20 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
}
}
std::string to_string(const SummaryConfigNode::Category cat) {
switch( cat ) {
case SummaryConfigNode::Category::Aquifer: return "Aquifer";
case SummaryConfigNode::Category::Well: return "Well";
case SummaryConfigNode::Category::Group: return "Group";
case SummaryConfigNode::Category::Field: return "Field";
case SummaryConfigNode::Category::Region: return "Region";
case SummaryConfigNode::Category::Block: return "Block";
case SummaryConfigNode::Category::Connection: return "Connection";
case SummaryConfigNode::Category::Segment: return "Segment";
case SummaryConfigNode::Category::Node: return "Node";
case SummaryConfigNode::Category::Miscellaneous: return "Miscellaneous";
std::string to_string(const SummaryConfigNode::Category cat)
{
switch (cat) {
case SummaryConfigNode::Category::Aquifer: return "Aquifer";
case SummaryConfigNode::Category::Well: return "Well";
case SummaryConfigNode::Category::Group: return "Group";
case SummaryConfigNode::Category::Field: return "Field";
case SummaryConfigNode::Category::Region: return "Region";
case SummaryConfigNode::Category::Block: return "Block";
case SummaryConfigNode::Category::Connection: return "Connection";
case SummaryConfigNode::Category::Completion: return "Completion";
case SummaryConfigNode::Category::Segment: return "Segment";
case SummaryConfigNode::Category::Node: return "Node";
case SummaryConfigNode::Category::Miscellaneous: return "Miscellaneous";
}
throw std::invalid_argument {
@ -1299,40 +1307,84 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
}
}
inline void handleKW( SummaryConfig::keyword_list& list,
SummaryConfigContext& context,
const std::vector<std::string>& node_names,
const std::vector<int>& analyticAquiferIDs,
const std::vector<int>& numericAquiferIDs,
const DeckKeyword& keyword,
const Schedule& schedule,
const FieldPropsManager& field_props,
const ParseContext& parseContext,
ErrorGuard& errors,
const GridDims& dims) {
inline void handleKW( SummaryConfig::keyword_list& list,
SummaryConfigContext& context,
const std::vector<std::string>& node_names,
const std::vector<int>& analyticAquiferIDs,
const std::vector<int>& numericAquiferIDs,
const DeckKeyword& keyword,
const Schedule& schedule,
const FieldPropsManager& field_props,
const ParseContext& parseContext,
ErrorGuard& errors,
const GridDims& dims)
{
using Cat = SummaryConfigNode::Category;
const auto& name = keyword.name();
check_udq( keyword.location(), schedule, parseContext, errors );
check_udq(keyword.location(), schedule, parseContext, errors);
const auto cat = parseKeywordCategory( name );
switch( cat ) {
case Cat::Well: return keywordW( list, parseContext, errors, keyword, schedule );
case Cat::Group: return keywordG( list, parseContext, errors, keyword, schedule );
case Cat::Field: return keywordF( list, keyword );
case Cat::Block: return keywordB( list, keyword, dims );
case Cat::Region: return keywordR( list, context, keyword, schedule, field_props, parseContext, errors );
case Cat::Connection: return keywordC( list, parseContext, errors, keyword, schedule, dims);
case Cat::Segment: return keywordS( list, parseContext, errors, keyword, schedule );
case Cat::Node: return keyword_node( list, node_names, parseContext, errors, keyword );
case Cat::Aquifer: return keywordAquifer(list, analyticAquiferIDs, numericAquiferIDs, parseContext, errors, keyword);
case Cat::Miscellaneous: return keywordMISC( list, keyword );
const auto cat = parseKeywordCategory(name);
switch (cat) {
case Cat::Well:
keywordW(list, parseContext, errors, keyword, schedule);
break;
default:
std::string msg_fmt = fmt::format("Summary output keyword {{keyword}} of type {} is not supported\n"
"In {{file}} line {{line}}", to_string(cat));
parseContext.handleError(ParseContext::SUMMARY_UNHANDLED_KEYWORD, msg_fmt, keyword.location(), errors);
return;
case Cat::Group:
keywordG(list, parseContext, errors, keyword, schedule);
break;
case Cat::Field:
keywordF(list, keyword);
break;
case Cat::Block:
keywordB(list, keyword, dims);
break;
case Cat::Region:
keywordR(list, context, keyword, schedule, field_props, parseContext, errors);
break;
case Cat::Connection:
keywordC(list, parseContext, errors, keyword, schedule, dims);
break;
case Cat::Completion:
if (is_well_completion(name)) {
keywordWL(list, parseContext, errors, keyword, schedule);
}
else {
keywordCL(list, parseContext, errors, keyword, schedule, dims);
}
break;
case Cat::Segment:
keywordS(list, parseContext, errors, keyword, schedule);
break;
case Cat::Node:
keyword_node(list, node_names, parseContext, errors, keyword);
break;
case Cat::Aquifer:
keywordAquifer(list, analyticAquiferIDs, numericAquiferIDs, parseContext, errors, keyword);
break;
case Cat::Miscellaneous:
keywordMISC(list, keyword);
break;
default: {
const auto msg_fmt = fmt::format("Summary output keyword {{keyword}} of "
"type {} is not supported\n"
"In {{file}} line {{line}}",
to_string(cat));
parseContext.handleError(ParseContext::SUMMARY_UNHANDLED_KEYWORD,
msg_fmt, keyword.location(), errors);
}
break;
}
}
@ -1343,24 +1395,45 @@ inline void handleKW( SummaryConfig::keyword_list& list,
const KeywordLocation& location,
const Schedule& schedule,
const ParseContext& /* parseContext */,
ErrorGuard& /* errors */) {
if (is_udq(keyword))
throw std::logic_error("UDQ keywords not handleded when expanding alias list");
ErrorGuard& /* errors */)
{
if (is_udq(keyword)) {
throw std::logic_error {
"UDQ keywords not handleded when expanding alias list"
};
}
using Cat = SummaryConfigNode::Category;
const auto cat = parseKeywordCategory( keyword );
const auto cat = parseKeywordCategory(keyword);
switch( cat ) {
case Cat::Well: return keywordW( list, keyword, location, schedule );
case Cat::Group: return keywordG( list, keyword, location, schedule );
case Cat::Field: return keywordF( list, keyword, location );
case Cat::Aquifer: return keywordAquifer( list, keyword, analyticAquiferIDs, numericAquiferIDs, location );
case Cat::Miscellaneous: return keywordMISC( list, keyword, location);
switch (cat) {
case Cat::Well:
keywordW(list, keyword, location, schedule);
break;
default:
throw std::logic_error("Keyword type: " + to_string( cat ) + " is not supported in alias lists. Internal error handling: " + keyword);
case Cat::Group:
keywordG(list, keyword, location, schedule);
break;
case Cat::Field:
keywordF(list, keyword, location);
break;
case Cat::Aquifer:
keywordAquifer(list, keyword, analyticAquiferIDs,
numericAquiferIDs, location);
break;
case Cat::Miscellaneous:
keywordMISC(list, keyword, location);
break;
default:
throw std::logic_error {
fmt::format("Keyword type {} is not supported in alias "
"lists. Internal error handling keyword {}",
to_string(cat), keyword)
};
}
}
@ -1395,7 +1468,8 @@ inline void handleKW( SummaryConfig::keyword_list& list,
// =====================================================================
SummaryConfigNode::Type parseKeywordType(std::string keyword) {
SummaryConfigNode::Type parseKeywordType(std::string keyword)
{
if (is_well_completion(keyword))
keyword.pop_back();
@ -1413,20 +1487,21 @@ SummaryConfigNode::Type parseKeywordType(std::string keyword) {
return SummaryConfigNode::Type::Undefined;
}
SummaryConfigNode::Category parseKeywordCategory(const std::string& keyword) {
SummaryConfigNode::Category parseKeywordCategory(const std::string& keyword)
{
using Cat = SummaryConfigNode::Category;
if (is_special(keyword)) { return Cat::Miscellaneous; }
switch (keyword[0]) {
case 'A': if (is_aquifer(keyword)) return Cat::Aquifer; break;
case 'W': return Cat::Well;
case 'G': return distinguish_group_from_node(keyword);
case 'F': return Cat::Field;
case 'C': return Cat::Connection;
case 'R': return Cat::Region;
case 'B': return Cat::Block;
case 'S': return Cat::Segment;
case 'A': if (is_aquifer(keyword)) return Cat::Aquifer; break;
case 'W': return distinguish_well_from_completion(keyword);
case 'G': return distinguish_group_from_node(keyword);
case 'F': return Cat::Field;
case 'C': return distinguish_connection_from_completion(keyword);
case 'R': return Cat::Region;
case 'B': return Cat::Block;
case 'S': return Cat::Segment;
}
// TCPU, MLINEARS, NEWTON, &c
@ -1434,10 +1509,10 @@ SummaryConfigNode::Category parseKeywordCategory(const std::string& keyword) {
}
SummaryConfigNode::SummaryConfigNode(std::string keyword, const Category cat, KeywordLocation loc_arg) :
keyword_(std::move(keyword)),
category_(cat),
loc(std::move(loc_arg))
SummaryConfigNode::SummaryConfigNode(std::string keyword, const Category cat, KeywordLocation loc_arg)
: keyword_ (std::move(keyword))
, category_(cat)
, loc (std::move(loc_arg))
{}
SummaryConfigNode SummaryConfigNode::serializeObject()
@ -1503,6 +1578,7 @@ std::string SummaryConfigNode::uniqueNodeKey() const
return this->keyword() + ':' + std::to_string(this->number());
case SummaryConfigNode::Category::Connection: [[fallthrough]];
case SummaryConfigNode::Category::Completion: [[fallthrough]];
case SummaryConfigNode::Category::Segment:
return this->keyword() + ':' + this->namedEntity() + ':' + std::to_string(this->number());
}
@ -1538,6 +1614,7 @@ bool operator==(const SummaryConfigNode& lhs, const SummaryConfigNode& rhs)
return lhs.number() == rhs.number();
case SummaryConfigNode::Category::Connection: [[fallthrough]];
case SummaryConfigNode::Category::Completion: [[fallthrough]];
case SummaryConfigNode::Category::Segment:
// Equal if associated to same numeric
// sub-entity of same named entity
@ -1575,6 +1652,7 @@ bool operator<(const SummaryConfigNode& lhs, const SummaryConfigNode& rhs)
return lhs.number() < rhs.number();
case SummaryConfigNode::Category::Connection: [[fallthrough]];
case SummaryConfigNode::Category::Completion: [[fallthrough]];
case SummaryConfigNode::Category::Segment:
{
// Ordering determined by pair of named entity and numeric ID.

View File

@ -18,9 +18,12 @@
#include <opm/io/eclipse/ESmry.hpp>
#include <opm/io/eclipse/SummaryNode.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/utility/shmatch.hpp>
#include <opm/common/utility/TimeService.hpp>
#include <opm/io/eclipse/EclFile.hpp>
#include <opm/io/eclipse/EclUtil.hpp>
#include <opm/io/eclipse/EclOutput.hpp>
@ -34,10 +37,13 @@
#include <fstream>
#include <iterator>
#include <limits>
#include <regex>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include <fmt/format.h>
@ -46,19 +52,21 @@
KEYWORDS WGNAMES NUMS | PARAM index Corresponding ERT key
---------------------------------------------------+--------------------------------------------------
WGOR OP_1 0 | 0 WGOR:OP_1
FOPT :+:+:+:+ 0 | 1 FOPT
WWCT OP_1 0 | 2 WWCT:OP_1
WIR OP_1 0 | 3 WIR:OP_1
WGOR WI_1 0 | 4 WWCT:OP_1
WWCT W1_1 0 | 5 WWCT:WI_1
BPR :+:+:+:+ 12675 | 6 BPR:12675, BPR:i,j,k
RPR :+:+:+:+ 1 | 7 RPR:1
FOPT :+:+:+:+ 0 | 8 FOPT
GGPR NORTH 0 | 9 GGPR:NORTH
COPR OP_1 5628 | 10 COPR:OP_1:56286, COPR:OP_1:i,j,k
RXF :+:+:+:+ R1 + 32768*(R2 + 10) | 11 RXF:2-3
SOFX OP_1 12675 | 12 SOFX:OP_1:12675, SOFX:OP_1:i,j,jk
AAQX :+:+:+:+ 12 | 13 AAQX:12
WOPRL__1 OP_1 1 | 1 WOPRL:OP_1:1 -- KEYWORDS is strictly speaking "WOPRL__1" here.
FOPT :+:+:+:+ 0 | 2 FOPT
WWCT OP_1 0 | 3 WWCT:OP_1
WIR OP_1 0 | 4 WIR:OP_1
WGOR WI_1 0 | 5 WWCT:OP_1
WWCT W1_1 0 | 6 WWCT:WI_1
BPR :+:+:+:+ 12675 | 7 BPR:12675, BPR:i,j,k
RPR :+:+:+:+ 1 | 8 RPR:1
FOPT :+:+:+:+ 0 | 9 FOPT
GGPR NORTH 0 | 10 GGPR:NORTH
COPR OP_1 5628 | 11 COPR:OP_1:56286, COPR:OP_1:i,j,k
COPRL OP_1 5628 | 12 COPRL:OP_1:5628, COPRL:OP_1:i,j,k
RXF :+:+:+:+ R1 + 32768*(R2 + 10) | 13 RXF:2-3
SOFX OP_1 12675 | 14 SOFX:OP_1:12675, SOFX:OP_1:i,j,jk
AAQX :+:+:+:+ 12 | 15 AAQX:12
*/
@ -85,6 +93,25 @@ Opm::time_point make_date(const std::vector<int>& datetime) {
return Opm::TimeService::from_time_t( Opm::asTimeT(ts) );
}
bool is_connection_completion(const std::string& keyword)
{
static const auto conn_compl_kw = std::regex {
R"(C[OGW][IP][RT]L)"
};
return std::regex_match(keyword, conn_compl_kw);
}
bool is_well_completion(const std::string& keyword)
{
static const auto well_compl_kw = std::regex {
R"(W[OGWLV][PIGOLCF][RT]L([0-9_]{2}[0-9])?)"
};
// True, e.g., for WOPRL, WOPRL__8, WOPRL123, but not WOPRL___ or
// WKITL.
return std::regex_match(keyword, well_compl_kw);
}
}
@ -186,13 +213,17 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
for (unsigned int i=0; i<keywords.size(); i++) {
Opm::EclIO::lgr_info lgr { lgrs[i], {numlx[i], numly[i], numlz[i]}};
const std::string keyString = makeKeyString(keywords[i], wgnames[i], nums[i], lgr);
const auto category = SummaryNode::category_from_keyword(keywords[i]);
const auto normKw = SummaryNode::normalise_keyword(category, keywords[i]);
const std::string keyString = makeKeyString(normKw, wgnames[i], nums[i], lgr);
combindKeyList.push_back(keyString);
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i]),
normKw,
category,
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -207,13 +238,16 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
} else {
for (unsigned int i=0; i<keywords.size(); i++) {
const std::string keyString = makeKeyString(keywords[i], wgnames[i], nums[i], {});
const auto category = SummaryNode::category_from_keyword(keywords[i]);
const auto normKw = SummaryNode::normalise_keyword(category, keywords[i]);
const std::string keyString = makeKeyString(normKw, wgnames[i], nums[i], {});
combindKeyList.push_back(keyString);
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i]),
normKw,
category,
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -317,14 +351,17 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (have_lgr) {
for (size_t i = 0; i < keywords.size(); i++) {
Opm::EclIO::lgr_info lgr { lgrs[i], {numlx[i], numly[i], numlz[i]}};
const std::string keyString = makeKeyString(keywords[i], wgnames[i], nums[i], lgr);
const auto category = SummaryNode::category_from_keyword(keywords[i]);
const auto normKw = SummaryNode::normalise_keyword(category, keywords[i]);
const std::string keyString = makeKeyString(normKw, wgnames[i], nums[i], lgr);
combindKeyList.push_back(keyString);
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i]),
normKw,
category,
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -341,13 +378,16 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
for (size_t i = 0; i < keywords.size(); i++) {
const std::string keyString = makeKeyString(keywords[i], wgnames[i], nums[i], {});
const auto category = SummaryNode::category_from_keyword(keywords[i]);
const auto normKw = SummaryNode::normalise_keyword(category, keywords[i]);
const std::string keyString = makeKeyString(normKw, wgnames[i], nums[i], {});
combindKeyList.push_back(keyString);
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i]),
normKw,
category,
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -425,13 +465,16 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (have_lgr) {
for (size_t i=0; i < keywords.size(); i++) {
Opm::EclIO::lgr_info lgr { lgrs[i], {numlx[i], numly[i], numlz[i]}};
const std::string keyw = makeKeyString(keywords[i], wgnames[i], nums[i], lgr);
const auto normKw = SummaryNode::normalise_keyword(keywords[i]);
const std::string keyw = makeKeyString(normKw, wgnames[i], nums[i], lgr);
if ((!keyw.empty()) && (keywList.find(keyw) != keywList.end()))
arrayPos[specInd][keyIndex[keyw]]=i;
}
} else {
for (size_t i=0; i < keywords.size(); i++) {
const std::string keyw = makeKeyString(keywords[i], wgnames[i], nums[i], {});
const auto normKw = SummaryNode::normalise_keyword(keywords[i]);
const std::string keyw = makeKeyString(normKw, wgnames[i], nums[i], {});
if ((!keyw.empty()) && (keywList.find(keyw) != keywList.end()))
arrayPos[specInd][keyIndex[keyw]]=i;
}
@ -1251,6 +1294,10 @@ std::string ESmry::makeKeyString(const std::string& keywordArg, const std::strin
return "";
}
if (is_well_completion(keywordArg)) {
return fmt::format("{}:{}:{}", keywordArg, wgname, num);
}
return fmt::format("{}:{}", keywordArg, wgname);
}
@ -1259,16 +1306,20 @@ std::string ESmry::makeKeyString(const std::string& keywordArg, const std::strin
std::string ESmry::unpackNumber(const SummaryNode& node) const
{
if (node.category == SummaryNode::Category::Block ||
node.category == SummaryNode::Category::Connection) {
if ((node.category == SummaryNode::Category::Block) ||
(node.category == SummaryNode::Category::Connection) ||
((node.category == SummaryNode::Category::Completion) &&
is_connection_completion(node.keyword)))
{
int _i,_j,_k;
ijk_from_global_index(node.number, _i, _j, _k);
return fmt::format("{},{},{}", _i, _j, _k);
}
else if (node.category == SummaryNode::Category::Region && node.keyword[2] == 'F') {
const auto r1 = node.number % (1 << 15);
const auto r2 = (node.number / (1 << 15)) - 10;
else if ((node.category == SummaryNode::Category::Region) &&
(node.keyword[2] == 'F'))
{
const auto& [r1, r2] = splitSummaryNumber(node.number);
return fmt::format("{}-{}", r1, r2);
}

View File

@ -32,6 +32,7 @@ constexpr bool use_number(Opm::EclIO::SummaryNode::Category category) {
case Opm::EclIO::SummaryNode::Category::Aquifer: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Block: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Connection: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Completion: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Region: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Segment:
return true;
@ -50,6 +51,7 @@ constexpr bool use_number(Opm::EclIO::SummaryNode::Category category) {
constexpr bool use_name(Opm::EclIO::SummaryNode::Category category) {
switch (category) {
case Opm::EclIO::SummaryNode::Category::Connection: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Completion: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Group: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Segment: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Node: [[fallthrough]];
@ -80,6 +82,25 @@ bool is_node_keyword(const std::string& keyword)
return node_kw.find(keyword) != node_kw.end();
}
bool is_connection_completion(const std::string& keyword)
{
static const auto conn_compl_kw = std::regex {
R"(C[OGW][IP][RT]L)"
};
return std::regex_match(keyword, conn_compl_kw);
}
bool is_well_completion(const std::string& keyword)
{
static const auto well_compl_kw = std::regex {
R"(W[OGWLV][PIGOLCF][RT]L([0-9_]{2}[0-9])?)"
};
// True, e.g., for WOPRL, WOPRL__8, WOPRL123, but not WOPRL___ or WKITL.
return std::regex_match(keyword, well_compl_kw);
}
Opm::EclIO::SummaryNode::Category
distinguish_group_from_node(const std::string& keyword)
{
@ -87,10 +108,42 @@ distinguish_group_from_node(const std::string& keyword)
? Opm::EclIO::SummaryNode::Category::Node
: Opm::EclIO::SummaryNode::Category::Group;
}
Opm::EclIO::SummaryNode::Category
distinguish_connection_from_completion(const std::string& keyword)
{
return is_connection_completion(keyword)
? Opm::EclIO::SummaryNode::Category::Completion
: Opm::EclIO::SummaryNode::Category::Connection;
}
std::string Opm::EclIO::SummaryNode::unique_key(number_renderer render_number) const {
std::vector<std::string> key_parts { keyword } ;
Opm::EclIO::SummaryNode::Category
distinguish_well_from_completion(const std::string& keyword)
{
return is_well_completion(keyword)
? Opm::EclIO::SummaryNode::Category::Completion
: Opm::EclIO::SummaryNode::Category::Well;
}
std::string normalise_well_completion_keyword(const std::string& keyword)
{
static const auto well_compl_kw = std::regex {
R"((W[OGWLV][PIGOLCF][RT]L)([0-9_]{2}[0-9])?)"
};
auto keywordPieces = std::smatch {};
if (std::regex_match(keyword, keywordPieces, well_compl_kw)) {
return keywordPieces[1].str();
}
return keyword;
}
} // Anonymous namespace
std::string Opm::EclIO::SummaryNode::unique_key(number_renderer render_number) const
{
auto key_parts = std::vector<std::string> { normalise_keyword(this->category, this->keyword) };
if (auto opt = display_name())
key_parts.emplace_back(opt.value());
@ -146,43 +199,50 @@ bool Opm::EclIO::SummaryNode::is_user_defined() const {
return matched && !blacklisted;
}
/*
Observe that this function started out as a slight generalisation of the
special case handling of segment variables; i.e. variables starting with 'S'.
In general there are many other expecptions e.g. 'NEWTON' is an Miscellaneous
variable and not a network variable - but they will be added when/if required.
*/
bool Opm::EclIO::SummaryNode::miscellaneous_exception(const std::string& keyword) {
bool Opm::EclIO::SummaryNode::miscellaneous_exception(const std::string& keyword)
{
static const std::unordered_set<std::string> miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"};
return miscellaneous_keywords.count(keyword) == 1;
}
Opm::EclIO::SummaryNode::Category Opm::EclIO::SummaryNode::category_from_keyword(
const std::string& keyword
) {
static const std::unordered_set<std::string> miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"};
if (keyword.length() == 0) {
Opm::EclIO::SummaryNode::Category
Opm::EclIO::SummaryNode::category_from_keyword(const std::string& keyword)
{
if (keyword.empty() ||
Opm::EclIO::SummaryNode::miscellaneous_exception(keyword))
{
return Category::Miscellaneous;
}
if (Opm::EclIO::SummaryNode::miscellaneous_exception(keyword))
return Category::Miscellaneous;
switch (keyword[0]) {
case 'A': return Category::Aquifer;
case 'B': return Category::Block;
case 'C': return Category::Connection;
case 'C': return distinguish_connection_from_completion(keyword);
case 'F': return Category::Field;
case 'G': return distinguish_group_from_node(keyword);
case 'R': return Category::Region;
case 'S': return Category::Segment;
case 'W': return Category::Well;
case 'W': return distinguish_well_from_completion(keyword);
default: return Category::Miscellaneous;
}
}
std::string
Opm::EclIO::SummaryNode::normalise_keyword(const Opm::EclIO::SummaryNode::Category category,
const std::string& keyword)
{
return ((category == Opm::EclIO::SummaryNode::Category::Completion) &&
is_well_completion(keyword))
? normalise_well_completion_keyword(keyword)
: keyword;
}
std::optional<std::string> Opm::EclIO::SummaryNode::display_name() const {
if (use_name(category)) {
return wgname;

View File

@ -99,6 +99,7 @@ template <> struct fmt::formatter<Opm::EclIO::SummaryNode::Category>: fmt::forma
case Category::Region: name = "Region"; break;
case Category::Block: name = "Block"; break;
case Category::Connection: name = "Connection"; break;
case Category::Completion: name = "Completion"; break;
case Category::Segment: name = "Segment"; break;
case Category::Aquifer: name = "Aquifer"; break;
case Category::Node: name = "Node"; break;
@ -817,7 +818,7 @@ inline quantity cpr( const fn_args& args ) {
// The args.num value is the literal value which will go to the
// NUMS array in the eclipse SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
// up a connection with offset 0.
const size_t global_index = args.num - 1;
if (args.schedule_wells.empty())
return zero;
@ -920,7 +921,7 @@ inline quantity crate( const fn_args& args ) {
// The args.num value is the literal value which will go to the
// NUMS array in the eclipse SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
// up a connection with offset 0.
const size_t global_index = args.num - 1;
if (args.schedule_wells.empty())
return zero;
@ -975,7 +976,7 @@ quantity crate_resv( const fn_args& args ) {
// The args.num value is the literal value which will go to the
// NUMS array in the eclipse SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
// up a connection with offset 0.
const auto global_index = static_cast<std::size_t>(args.num - 1);
const auto& well_data = xwPos->second;
@ -1004,7 +1005,7 @@ inline quantity srate( const fn_args& args ) {
// The args.num value is the literal value which will go to the
// NUMS array in the eclispe SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
// up a connection with offset 0.
if (args.schedule_wells.empty()) {
return zero;
}
@ -1049,7 +1050,7 @@ inline quantity trans_factors ( const fn_args& args ) {
// No dynamic results for this well. Not open?
return zero;
// Like completion rate we need to look up a connection with offset 0.
// Like connection rate we need to look up a connection with offset 0.
const size_t global_index = args.num - 1;
const auto& connections = xwPos->second.connections;
auto connPos = std::find_if(connections.begin(), connections.end(),
@ -1082,7 +1083,7 @@ inline quantity segpress ( const fn_args& args )
return zero;
}
// Like completion rate we need to look up a connection with offset 0.
// Like connection rate we need to look up a connection with offset 0.
const size_t segNumber = args.num;
const auto& well_data = xwPos->second;
@ -1410,7 +1411,7 @@ inline quantity connection_productivity_index(const fn_args& args)
// The args.num value is the literal value which will go to the
// NUMS array in the eclipse SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
// up a connection with offset 0.
const auto global_index = static_cast<std::size_t>(args.num) - 1;
const auto& xcon = xwPos->second.connections;
@ -2429,6 +2430,7 @@ find_wells(const Opm::Schedule& schedule,
switch (node.category) {
case Opm::EclIO::SummaryNode::Category::Well:
case Opm::EclIO::SummaryNode::Category::Connection:
case Opm::EclIO::SummaryNode::Category::Completion:
case Opm::EclIO::SummaryNode::Category::Segment:
return find_single_well(schedule, node.wgname, sim_step);
@ -2450,7 +2452,7 @@ find_wells(const Opm::Schedule& schedule,
throw std::runtime_error {
fmt::format("Unhandled summary node category \"{}\" in find_wells()",
node.category)
static_cast<int>(node.category))
};
}
@ -2463,6 +2465,7 @@ bool need_wells(const Opm::EclIO::SummaryNode& node)
switch (node.category) {
case Cat::Connection: [[fallthrough]];
case Cat::Completion: [[fallthrough]];
case Cat::Field: [[fallthrough]];
case Cat::Group: [[fallthrough]];
case Cat::Segment: [[fallthrough]];
@ -2484,7 +2487,10 @@ bool need_wells(const Opm::EclIO::SummaryNode& node)
return false;
}
throw std::runtime_error("Unhandled summary node category in need_wells");
throw std::runtime_error {
fmt::format("Unhandled summary node category \"{}\" in need_wells()",
static_cast<int>(node.category))
};
}
void updateValue(const Opm::EclIO::SummaryNode& node, const double value, Opm::SummaryState& st)
@ -3396,7 +3402,10 @@ namespace Evaluator {
bool Factory::isFunctionRelation()
{
auto pos = funs.find(this->node_->keyword);
const auto normKw = Opm::EclIO::SummaryNode::
normalise_keyword(this->node_->category, this->node_->keyword);
auto pos = funs.find(normKw);
if (pos != funs.end()) {
// 'node_' represents a functional relation.
// Capture evaluation function and return true.
@ -3404,41 +3413,45 @@ namespace Evaluator {
return true;
}
auto keyword = this->node_->keyword;
auto dash_pos = keyword.find("_");
if (dash_pos != std::string::npos)
keyword = keyword.substr(0, dash_pos);
pos = funs.find(keyword);
if (pos != funs.end()) {
// 'node_' represents a functional relation.
// Capture evaluation function and return true.
this->paramFunction_ = pos->second;
return true;
if (normKw.length() <= std::string::size_type{4}) {
return false;
}
if (keyword.length() > 4 ) {
std::string tracer_tag = keyword.substr(0, 4);
std::string tracer_name = keyword.substr(4);
const auto& tracers = es_.tracer();
for (const auto& tracer : tracers) {
if (tracer.name == tracer_name) {
if (tracer.phase == Opm::Phase::WATER)
tracer_tag += "#W";
else if (tracer.phase == Opm::Phase::OIL)
tracer_tag += "#O";
else if (tracer.phase == Opm::Phase::GAS)
tracer_tag += "#G";
const auto tracer_name = normKw.substr(4);
pos = funs.find(tracer_tag);
if (pos != funs.end()) {
this->paramFunction_ = pos->second;
return true;
}
const auto& tracers = this->es_.tracer();
auto trPos = std::find_if(tracers.begin(), tracers.end(),
[&tracer_name](const auto& tracer)
{
return tracer.name == tracer_name;
});
break;
}
}
if (trPos == tracers.end()) {
return false;
}
auto tracer_tag = normKw.substr(0, 4);
switch (trPos->phase) {
case Opm::Phase::WATER:
tracer_tag += "#W";
break;
case Opm::Phase::OIL:
tracer_tag += "#O";
break;
case Opm::Phase::GAS:
tracer_tag += "#G";
break;
default:
return false;
}
pos = funs.find(tracer_tag);
if (pos != funs.end()) {
this->paramFunction_ = pos->second;
return true;
}
return false;

View File

@ -445,21 +445,12 @@ double ecl_sum_get_general_var(const EclIO::ESmry* smry,
return smry->get(var)[timeIdx];
}
#if 0
bool ecl_sum_has_well_var( const EclIO::ESmry* smry,
const std::string& wellname,
const std::string& variable )
{
return smry->hasKey(variable + ':' + wellname);
}
#endif
double ecl_sum_get_well_var( const EclIO::ESmry* smry,
const int timeIdx,
const std::string& wellname,
const std::string& variable )
{
return smry->get(variable + ':' + wellname)[timeIdx];
return smry->get(fmt::format("{}:{}", variable, wellname))[timeIdx];
}
double ecl_sum_get_group_var( const EclIO::ESmry* smry,
@ -467,7 +458,16 @@ double ecl_sum_get_group_var( const EclIO::ESmry* smry,
const std::string& groupname,
const std::string& variable )
{
return smry->get(variable + ':' + groupname)[timeIdx];
return smry->get(fmt::format("{}:{}", variable, groupname))[timeIdx];
}
double ecl_sum_get_well_completion_var( const EclIO::ESmry* smry,
const int timeIdx,
const std::string& wellname,
const std::string& variable,
const int completion)
{
return smry->get(fmt::format("{}:{}:{}", variable, wellname, completion))[timeIdx];
}
double ecl_sum_get_well_connection_var( const EclIO::ESmry* smry,
@ -478,8 +478,7 @@ double ecl_sum_get_well_connection_var( const EclIO::ESmry* smry,
const int j,
const int k)
{
const auto ijk = std::to_string(i) + ',' + std::to_string(j) + ',' + std::to_string(k);
return smry->get(variable + ':' + wellname + ':' + ijk)[timeIdx];
return smry->get(fmt::format("{}:{}:{},{},{}", variable, wellname, i, j, k))[timeIdx];
}
bool ecl_sum_has_well_connection_var( const EclIO::ESmry* smry,
@ -489,7 +488,7 @@ bool ecl_sum_has_well_connection_var( const EclIO::ESmry* smry,
const int j,
const int k)
{
const auto key = fmt::format("{}:{}:{},{},{}", wellname, variable, i, j, k);
const auto key = fmt::format("{}:{}:{},{},{}", variable, wellname, i, j, k);
return ecl_sum_has_key(smry, key);
}
@ -497,7 +496,6 @@ struct setup {
Deck deck;
EclipseState es;
const EclipseGrid& grid;
std::shared_ptr<Python> python;
Schedule schedule;
SummaryConfig config;
data::Wells wells;
@ -511,8 +509,7 @@ struct setup {
deck( Parser().parseFile( path) ),
es( deck ),
grid( es.getInputGrid() ),
python( std::make_shared<Python>() ),
schedule( deck, es, python),
schedule( deck, es, std::make_shared<Python>()),
config( deck, schedule, es.fieldProps(), es.aquifer()),
wells( result_wells(w3_injector) ),
grp_nwrk( result_group_nwrk() ),
@ -520,6 +517,7 @@ struct setup {
ta( "summary_test" )
{}
};
} // Anonymous namespace
BOOST_AUTO_TEST_SUITE(Summary)
@ -1265,13 +1263,13 @@ BOOST_AUTO_TEST_CASE(connection_kewords) {
BOOST_CHECK_CLOSE( 234.5, ecl_sum_get_well_connection_var( resp, 2, "W_2", "CVPR", 2, 1, 2 ), 1e-5 );
BOOST_CHECK_CLOSE( 0.0, ecl_sum_get_well_connection_var( resp, 1, "W_3", "CVPR", 3, 1, 1 ), 1e-5 );
BOOST_CHECK_CLOSE(ecl_sum_get_well_var(resp, 1, "W_1", "WOPRL__1"), ecl_sum_get_well_connection_var(resp, 1, "W_1", "COPR", 1,1,1), 1e-5);
BOOST_CHECK_CLOSE(ecl_sum_get_well_var(resp, 1, "W_2", "WOPRL__2"), ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPR", 2,1,1) +
BOOST_CHECK_CLOSE(ecl_sum_get_well_completion_var(resp, 1, "W_1", "WOPRL", 1), ecl_sum_get_well_connection_var(resp, 1, "W_1", "COPR", 1,1,1), 1e-5);
BOOST_CHECK_CLOSE(ecl_sum_get_well_completion_var(resp, 1, "W_2", "WOPRL", 2), ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPR", 2,1,1) +
ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPR", 2,1,2), 1e-5);
BOOST_CHECK_CLOSE(ecl_sum_get_well_var(resp, 1, "W_3", "WOPRL__3"), ecl_sum_get_well_connection_var(resp, 1, "W_3", "COPR", 3,1,1), 1e-5);
BOOST_CHECK_EQUAL(ecl_sum_get_well_var(resp, 1, "W_2", "WOPRL__2"), ecl_sum_get_well_var(resp, 1, "W_2", "WOFRL__2"));
BOOST_CHECK_CLOSE(ecl_sum_get_well_completion_var(resp, 1, "W_3", "WOPRL", 3), ecl_sum_get_well_connection_var(resp, 1, "W_3", "COPR", 3,1,1), 1e-5);
BOOST_CHECK_EQUAL(ecl_sum_get_well_completion_var(resp, 1, "W_2", "WOPRL", 2), ecl_sum_get_well_completion_var(resp, 1, "W_2", "WOFRL", 2));
BOOST_CHECK_CLOSE(ecl_sum_get_well_var(resp, 1, "W_1", "WOPRL__1"), ecl_sum_get_well_connection_var(resp, 1, "W_1", "COPRL", 1,1,1), 1e-5);
BOOST_CHECK_CLOSE(ecl_sum_get_well_completion_var(resp, 1, "W_1", "WOPRL", 1), ecl_sum_get_well_connection_var(resp, 1, "W_1", "COPRL", 1,1,1), 1e-5);
BOOST_CHECK_CLOSE(ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPRL", 2, 1, 1), ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPR", 2,1,1) +
ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPR", 2,1,2), 1e-5);
BOOST_CHECK_CLOSE(ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPRL", 2, 1, 2), ecl_sum_get_well_connection_var(resp, 1, "W_2", "COPR", 2,1,1) +

View File

@ -23,11 +23,16 @@
#include <opm/io/eclipse/SummaryNode.hpp>
#include <initializer_list>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
void expect_key(const Opm::EclIO::SummaryNode& node, const std::string& unique_key) {
BOOST_CHECK_EQUAL(node.unique_key(), unique_key);
}
}
} // Anonymous namespace
BOOST_AUTO_TEST_SUITE(UniqueKey)
@ -86,3 +91,100 @@ BOOST_AUTO_TEST_CASE(user_defined) {
}
BOOST_AUTO_TEST_SUITE_END() // UniqueKey
// ===========================================================================
BOOST_AUTO_TEST_SUITE(Category)
namespace {
std::string to_string(const Opm::EclIO::SummaryNode::Category cat)
{
using Cat = Opm::EclIO::SummaryNode::Category;
switch (cat) {
case Cat::Aquifer: return "Aquifer";
case Cat::Well: return "Well";
case Cat::Group: return "Group";
case Cat::Field: return "Field";
case Cat::Region: return "Region";
case Cat::Block: return "Block";
case Cat::Connection: return "Connection";
case Cat::Completion: return "Completion";
case Cat::Segment: return "Segment";
case Cat::Node: return "Node";
case Cat::Miscellaneous: return "Miscellaneous";
}
throw std::invalid_argument {
"Unhandled Summary Parameter Category '"
+ std::to_string(static_cast<int>(cat)) + '\''
};
}
Opm::EclIO::SummaryNode::Category category(const std::string& kw)
{
return Opm::EclIO::SummaryNode::category_from_keyword(kw);
}
} // Anonymous namespace
BOOST_AUTO_TEST_CASE(Well)
{
const auto well_kw = std::vector<std::string> {
"WOPR", "WOPT", "WGIR", "WWIR",
};
for (const auto& kw : well_kw) {
BOOST_CHECK_MESSAGE(category(kw) == Opm::EclIO::SummaryNode::Category::Well,
"Keyword '" << kw << "' must be category 'Well'. Got '" <<
to_string(category(kw)) << "' instead");
}
BOOST_CHECK_MESSAGE(category("WOPRL") != Opm::EclIO::SummaryNode::Category::Well,
"Keyword 'WOPRL' must NOT be category 'Well'");
}
BOOST_AUTO_TEST_CASE(Connection)
{
const auto connection_kw = std::vector<std::string> {
"COPR", "COPT", "CGIR", "CWIR",
};
for (const auto& kw : connection_kw) {
BOOST_CHECK_MESSAGE(category(kw) == Opm::EclIO::SummaryNode::Category::Connection,
"Keyword '" << kw << "' must be category 'Connection'. Got '" <<
to_string(category(kw)) << "' instead");
}
BOOST_CHECK_MESSAGE(category("COPRL") != Opm::EclIO::SummaryNode::Category::Connection,
"Keyword 'COPRL' must NOT be category 'Connection'");
}
BOOST_AUTO_TEST_CASE(Completion)
{
const auto compl_kw = std::vector<std::string> {
"OPRL", "OPTL", "GIRL", "WIRL",
};
for (const auto& kw_base : compl_kw) {
const auto kw = 'C' + kw_base;
BOOST_CHECK_MESSAGE(category(kw) == Opm::EclIO::SummaryNode::Category::Completion,
"Keyword '" << kw << "' must be category 'Completion'. Got '" <<
to_string(category(kw)) << "' instead");
}
for (const auto* suffix : { "", "__1", "_12", "123" }) {
for (const auto& kw_base : compl_kw) {
const auto kw = 'W' + kw_base + suffix;
BOOST_CHECK_MESSAGE(category(kw) == Opm::EclIO::SummaryNode::Category::Completion,
"Keyword '" << kw << "' must be category 'Completion'. Got '" <<
to_string(category(kw)) << "' instead");
}
}
for (const auto* kw : { "WOPRLK", "CGIR", "WKITL__8", "WOOOOPRL", "WHIRL" }) {
BOOST_CHECK_MESSAGE(category(kw) != Opm::EclIO::SummaryNode::Category::Completion,
"Keyword '" << kw << "' must NOT be category 'Completion'");
}
}
BOOST_AUTO_TEST_SUITE_END() // Category