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

View File

@ -310,10 +310,6 @@ struct SummaryConfigContext {
&& is_in_set(countkw, keyword.substr(1)); && 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) bool is_supported_region_to_region(const std::string& keyword)
{ {
static const auto supported_kw = std::regex { static const auto supported_kw = std::regex {
@ -363,39 +359,22 @@ struct SummaryConfigContext {
bool is_connection_completion(const std::string& keyword) bool is_connection_completion(const std::string& keyword)
{ {
if (keyword[0] != 'C') static const auto conn_compl_kw = std::regex {
return false; R"(C[OGW][IP][RT]L)"
};
if (keyword.back() != 'L') return std::regex_match(keyword, conn_compl_kw);
return false;
if (is_udq(keyword))
return false;
if (keyword.size() != 5)
return false;
return true;
} }
bool is_well_completion(const std::string& keyword) bool is_well_completion(const std::string& keyword)
{ {
if (keyword[0] != 'W') static const auto well_compl_kw = std::regex {
return false; R"(W[OGWLV][PIGOLCF][RT]L([0-9_]{2}[0-9])?)"
};
if (keyword.back() != 'L') // True, e.g., for WOPRL, WOPRL__8, WOPRL123, but not WOPRL___ or
return false; // WKITL.
return std::regex_match(keyword, well_compl_kw);
if (is_liquid_phase(keyword))
return false;
if (is_udq(keyword))
return false;
if (keyword == "WMCTL")
return false;
return true;
} }
bool is_node_keyword(const std::string& keyword) bool is_node_keyword(const std::string& keyword)
@ -447,6 +426,21 @@ struct SummaryConfigContext {
: SummaryConfigNode::Category::Group; : 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) { 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" 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, inline void keywordCL(SummaryConfig::keyword_list& list,
const ParseContext& parseContext, const ParseContext& parseContext,
ErrorGuard& errors, ErrorGuard& errors,
const DeckKeyword& keyword, const DeckKeyword& keyword,
const Schedule& schedule , const Schedule& schedule,
const GridDims& dims) const GridDims& dims)
{ {
auto node = SummaryConfigNode{keyword.name(), SummaryConfigNode::Category::Connection, keyword.location()}; auto node = SummaryConfigNode {
node.parameterType( parseKeywordType(keyword.name()) ); keyword.name(), SummaryConfigNode::Category::Completion, keyword.location()
node.isUserDefined( is_udq(keyword.name()) ); }
.parameterType(parseKeywordType(keyword.name()))
.isUserDefined(is_udq(keyword.name()));
for (const auto& record : keyword) { for (const auto& record : keyword) {
const auto& pattern = record.getItem(0).get<std::string>(0); 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() ) const auto ijk_defaulted = record.getItem(1).defaultApplied(0);
handleMissingWell( parseContext, errors, keyword.location(), pattern );
const auto ijk_defaulted = record.getItem( 1 ).defaultApplied( 0 );
for (const auto& wname : well_names) { for (const auto& wname : well_names) {
const auto& well = schedule.getWellatEnd(wname); const auto& well = schedule.getWellatEnd(wname);
const auto& all_connections = well.getConnections(); const auto& all_connections = well.getConnections();
node.namedEntity( wname ); node.namedEntity(wname);
if (ijk_defaulted) { if (ijk_defaulted) {
for (const auto& conn : all_connections) 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 { } else {
const auto& ijk = getijk(record); const auto& ijk = getijk(record);
auto global_index = dims.getGlobalIndex(ijk[0], ijk[1], ijk[2]); auto global_index = dims.getGlobalIndex(ijk[0], ijk[1], ijk[2]);
@ -599,7 +595,8 @@ inline void keywordCL( SummaryConfig::keyword_list& list,
} else { } else {
std::string msg = fmt::format("Problem with keyword {{keyword}}\n" std::string msg = fmt::format("Problem with keyword {{keyword}}\n"
"In {{file}} line {{line}}\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); 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, const ParseContext& parseContext,
ErrorGuard& errors, ErrorGuard& errors,
const DeckKeyword& keyword, const DeckKeyword& keyword,
const Schedule& schedule ) const Schedule& schedule)
{ {
for (const auto& record : keyword) { for (const auto& record : keyword) {
const auto& pattern = record.getItem(0).get<std::string>(0); const auto& pattern = record.getItem(0).get<std::string>(0);
const int completion = record.getItem(1).get<int>(0); const auto well_names = schedule.wellNames(pattern, schedule.size() - 1);
auto well_names = schedule.wellNames( pattern, schedule.size() - 1 );
// We add the completion number both the extra field which contains if (well_names.empty()) {
// parsed data from the keywordname - i.e. WOPRL__8 and also to the handleMissingWell(parseContext, errors, keyword.location(), pattern);
// numeric member which will be written to the NUMS field. continue;
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() ) const auto completion = record.getItem(1).get<int>(0);
handleMissingWell( parseContext, errors, keyword.location(), pattern );
// 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) { for (const auto& wname : well_names) {
const auto& well = schedule.getWellatEnd(wname); if (schedule.getWellatEnd(wname).hasCompletion(completion)) {
if (well.hasCompletion(completion)) list.push_back(node.namedEntity(wname));
list.push_back( node.namedEntity( wname ) ); }
else { else {
std::string msg = fmt::format("Problem with keyword {{keyword}}\n" const auto msg = fmt::format("Problem with keyword {{keyword}}\n"
"In {{file}} line {{line}}\n" "In {{file}} line {{line}}\n"
"Completion number {} not defined for well {} ", completion, wname); "Completion number {} not defined for well {}",
parseContext.handleError( ParseContext::SUMMARY_UNHANDLED_KEYWORD, msg, keyword.location(), errors); 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, void keywordSWithRecords(const std::size_t last_timestep,
const ParseContext& parseContext, const ParseContext& parseContext,
ErrorGuard& errors, ErrorGuard& errors,
const DeckKeyword& keyword, const DeckKeyword& keyword,
const Schedule& schedule, const Schedule& schedule,
SummaryConfig::keyword_list& list) SummaryConfig::keyword_list& list)
@ -1213,7 +1219,7 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
inline void keywordS(SummaryConfig::keyword_list& list, inline void keywordS(SummaryConfig::keyword_list& list,
const ParseContext& parseContext, const ParseContext& parseContext,
ErrorGuard& errors, ErrorGuard& errors,
const DeckKeyword& keyword, const DeckKeyword& keyword,
const Schedule& schedule) const Schedule& schedule)
{ {
@ -1253,18 +1259,20 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
} }
} }
std::string to_string(const SummaryConfigNode::Category cat) { std::string to_string(const SummaryConfigNode::Category cat)
switch( cat ) { {
case SummaryConfigNode::Category::Aquifer: return "Aquifer"; switch (cat) {
case SummaryConfigNode::Category::Well: return "Well"; case SummaryConfigNode::Category::Aquifer: return "Aquifer";
case SummaryConfigNode::Category::Group: return "Group"; case SummaryConfigNode::Category::Well: return "Well";
case SummaryConfigNode::Category::Field: return "Field"; case SummaryConfigNode::Category::Group: return "Group";
case SummaryConfigNode::Category::Region: return "Region"; case SummaryConfigNode::Category::Field: return "Field";
case SummaryConfigNode::Category::Block: return "Block"; case SummaryConfigNode::Category::Region: return "Region";
case SummaryConfigNode::Category::Connection: return "Connection"; case SummaryConfigNode::Category::Block: return "Block";
case SummaryConfigNode::Category::Segment: return "Segment"; case SummaryConfigNode::Category::Connection: return "Connection";
case SummaryConfigNode::Category::Node: return "Node"; case SummaryConfigNode::Category::Completion: return "Completion";
case SummaryConfigNode::Category::Miscellaneous: return "Miscellaneous"; case SummaryConfigNode::Category::Segment: return "Segment";
case SummaryConfigNode::Category::Node: return "Node";
case SummaryConfigNode::Category::Miscellaneous: return "Miscellaneous";
} }
throw std::invalid_argument { throw std::invalid_argument {
@ -1299,40 +1307,84 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
} }
} }
inline void handleKW( SummaryConfig::keyword_list& list, inline void handleKW( SummaryConfig::keyword_list& list,
SummaryConfigContext& context, SummaryConfigContext& context,
const std::vector<std::string>& node_names, const std::vector<std::string>& node_names,
const std::vector<int>& analyticAquiferIDs, const std::vector<int>& analyticAquiferIDs,
const std::vector<int>& numericAquiferIDs, const std::vector<int>& numericAquiferIDs,
const DeckKeyword& keyword, const DeckKeyword& keyword,
const Schedule& schedule, const Schedule& schedule,
const FieldPropsManager& field_props, const FieldPropsManager& field_props,
const ParseContext& parseContext, const ParseContext& parseContext,
ErrorGuard& errors, ErrorGuard& errors,
const GridDims& dims) { const GridDims& dims)
{
using Cat = SummaryConfigNode::Category; using Cat = SummaryConfigNode::Category;
const auto& name = keyword.name(); const auto& name = keyword.name();
check_udq( keyword.location(), schedule, parseContext, errors ); check_udq(keyword.location(), schedule, parseContext, errors);
const auto cat = parseKeywordCategory( name ); const auto cat = parseKeywordCategory(name);
switch( cat ) { switch (cat) {
case Cat::Well: return keywordW( list, parseContext, errors, keyword, schedule ); case Cat::Well:
case Cat::Group: return keywordG( list, parseContext, errors, keyword, schedule ); keywordW(list, parseContext, errors, keyword, schedule);
case Cat::Field: return keywordF( list, keyword ); break;
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 );
default: case Cat::Group:
std::string msg_fmt = fmt::format("Summary output keyword {{keyword}} of type {} is not supported\n" keywordG(list, parseContext, errors, keyword, schedule);
"In {{file}} line {{line}}", to_string(cat)); break;
parseContext.handleError(ParseContext::SUMMARY_UNHANDLED_KEYWORD, msg_fmt, keyword.location(), errors);
return; 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 KeywordLocation& location,
const Schedule& schedule, const Schedule& schedule,
const ParseContext& /* parseContext */, const ParseContext& /* parseContext */,
ErrorGuard& /* errors */) { ErrorGuard& /* errors */)
{
if (is_udq(keyword)) {
if (is_udq(keyword)) throw std::logic_error {
throw std::logic_error("UDQ keywords not handleded when expanding alias list"); "UDQ keywords not handleded when expanding alias list"
};
}
using Cat = SummaryConfigNode::Category; using Cat = SummaryConfigNode::Category;
const auto cat = parseKeywordCategory( keyword ); const auto cat = parseKeywordCategory(keyword);
switch( cat ) { switch (cat) {
case Cat::Well: return keywordW( list, keyword, location, schedule ); case Cat::Well:
case Cat::Group: return keywordG( list, keyword, location, schedule ); keywordW(list, keyword, location, schedule);
case Cat::Field: return keywordF( list, keyword, location ); break;
case Cat::Aquifer: return keywordAquifer( list, keyword, analyticAquiferIDs, numericAquiferIDs, location );
case Cat::Miscellaneous: return keywordMISC( list, keyword, location);
default: case Cat::Group:
throw std::logic_error("Keyword type: " + to_string( cat ) + " is not supported in alias lists. Internal error handling: " + keyword); 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)) if (is_well_completion(keyword))
keyword.pop_back(); keyword.pop_back();
@ -1413,20 +1487,21 @@ SummaryConfigNode::Type parseKeywordType(std::string keyword) {
return SummaryConfigNode::Type::Undefined; return SummaryConfigNode::Type::Undefined;
} }
SummaryConfigNode::Category parseKeywordCategory(const std::string& keyword) { SummaryConfigNode::Category parseKeywordCategory(const std::string& keyword)
{
using Cat = SummaryConfigNode::Category; using Cat = SummaryConfigNode::Category;
if (is_special(keyword)) { return Cat::Miscellaneous; } if (is_special(keyword)) { return Cat::Miscellaneous; }
switch (keyword[0]) { switch (keyword[0]) {
case 'A': if (is_aquifer(keyword)) return Cat::Aquifer; break; case 'A': if (is_aquifer(keyword)) return Cat::Aquifer; break;
case 'W': return Cat::Well; case 'W': return distinguish_well_from_completion(keyword);
case 'G': return distinguish_group_from_node(keyword); case 'G': return distinguish_group_from_node(keyword);
case 'F': return Cat::Field; case 'F': return Cat::Field;
case 'C': return Cat::Connection; case 'C': return distinguish_connection_from_completion(keyword);
case 'R': return Cat::Region; case 'R': return Cat::Region;
case 'B': return Cat::Block; case 'B': return Cat::Block;
case 'S': return Cat::Segment; case 'S': return Cat::Segment;
} }
// TCPU, MLINEARS, NEWTON, &c // 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) : SummaryConfigNode::SummaryConfigNode(std::string keyword, const Category cat, KeywordLocation loc_arg)
keyword_(std::move(keyword)), : keyword_ (std::move(keyword))
category_(cat), , category_(cat)
loc(std::move(loc_arg)) , loc (std::move(loc_arg))
{} {}
SummaryConfigNode SummaryConfigNode::serializeObject() SummaryConfigNode SummaryConfigNode::serializeObject()
@ -1503,6 +1578,7 @@ std::string SummaryConfigNode::uniqueNodeKey() const
return this->keyword() + ':' + std::to_string(this->number()); return this->keyword() + ':' + std::to_string(this->number());
case SummaryConfigNode::Category::Connection: [[fallthrough]]; case SummaryConfigNode::Category::Connection: [[fallthrough]];
case SummaryConfigNode::Category::Completion: [[fallthrough]];
case SummaryConfigNode::Category::Segment: case SummaryConfigNode::Category::Segment:
return this->keyword() + ':' + this->namedEntity() + ':' + std::to_string(this->number()); 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(); return lhs.number() == rhs.number();
case SummaryConfigNode::Category::Connection: [[fallthrough]]; case SummaryConfigNode::Category::Connection: [[fallthrough]];
case SummaryConfigNode::Category::Completion: [[fallthrough]];
case SummaryConfigNode::Category::Segment: case SummaryConfigNode::Category::Segment:
// Equal if associated to same numeric // Equal if associated to same numeric
// sub-entity of same named entity // sub-entity of same named entity
@ -1575,6 +1652,7 @@ bool operator<(const SummaryConfigNode& lhs, const SummaryConfigNode& rhs)
return lhs.number() < rhs.number(); return lhs.number() < rhs.number();
case SummaryConfigNode::Category::Connection: [[fallthrough]]; case SummaryConfigNode::Category::Connection: [[fallthrough]];
case SummaryConfigNode::Category::Completion: [[fallthrough]];
case SummaryConfigNode::Category::Segment: case SummaryConfigNode::Category::Segment:
{ {
// Ordering determined by pair of named entity and numeric ID. // 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/ESmry.hpp>
#include <opm/io/eclipse/SummaryNode.hpp>
#include <opm/common/ErrorMacros.hpp> #include <opm/common/ErrorMacros.hpp>
#include <opm/common/utility/shmatch.hpp> #include <opm/common/utility/shmatch.hpp>
#include <opm/common/utility/TimeService.hpp> #include <opm/common/utility/TimeService.hpp>
#include <opm/io/eclipse/EclFile.hpp> #include <opm/io/eclipse/EclFile.hpp>
#include <opm/io/eclipse/EclUtil.hpp> #include <opm/io/eclipse/EclUtil.hpp>
#include <opm/io/eclipse/EclOutput.hpp> #include <opm/io/eclipse/EclOutput.hpp>
@ -34,10 +37,13 @@
#include <fstream> #include <fstream>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <regex>
#include <set> #include <set>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_set>
#include <vector>
#include <fmt/format.h> #include <fmt/format.h>
@ -46,19 +52,21 @@
KEYWORDS WGNAMES NUMS | PARAM index Corresponding ERT key KEYWORDS WGNAMES NUMS | PARAM index Corresponding ERT key
---------------------------------------------------+-------------------------------------------------- ---------------------------------------------------+--------------------------------------------------
WGOR OP_1 0 | 0 WGOR:OP_1 WGOR OP_1 0 | 0 WGOR:OP_1
FOPT :+:+:+:+ 0 | 1 FOPT WOPRL__1 OP_1 1 | 1 WOPRL:OP_1:1 -- KEYWORDS is strictly speaking "WOPRL__1" here.
WWCT OP_1 0 | 2 WWCT:OP_1 FOPT :+:+:+:+ 0 | 2 FOPT
WIR OP_1 0 | 3 WIR:OP_1 WWCT OP_1 0 | 3 WWCT:OP_1
WGOR WI_1 0 | 4 WWCT:OP_1 WIR OP_1 0 | 4 WIR:OP_1
WWCT W1_1 0 | 5 WWCT:WI_1 WGOR WI_1 0 | 5 WWCT:OP_1
BPR :+:+:+:+ 12675 | 6 BPR:12675, BPR:i,j,k WWCT W1_1 0 | 6 WWCT:WI_1
RPR :+:+:+:+ 1 | 7 RPR:1 BPR :+:+:+:+ 12675 | 7 BPR:12675, BPR:i,j,k
FOPT :+:+:+:+ 0 | 8 FOPT RPR :+:+:+:+ 1 | 8 RPR:1
GGPR NORTH 0 | 9 GGPR:NORTH FOPT :+:+:+:+ 0 | 9 FOPT
COPR OP_1 5628 | 10 COPR:OP_1:56286, COPR:OP_1:i,j,k GGPR NORTH 0 | 10 GGPR:NORTH
RXF :+:+:+:+ R1 + 32768*(R2 + 10) | 11 RXF:2-3 COPR OP_1 5628 | 11 COPR:OP_1:56286, COPR:OP_1:i,j,k
SOFX OP_1 12675 | 12 SOFX:OP_1:12675, SOFX:OP_1:i,j,jk COPRL OP_1 5628 | 12 COPRL:OP_1:5628, COPRL:OP_1:i,j,k
AAQX :+:+:+:+ 12 | 13 AAQX:12 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) ); 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++) { for (unsigned int i=0; i<keywords.size(); i++) {
Opm::EclIO::lgr_info lgr { lgrs[i], {numlx[i], numly[i], numlz[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); combindKeyList.push_back(keyString);
if (! keyString.empty()) { if (! keyString.empty()) {
summaryNodes.push_back( { summaryNodes.push_back( {
keywords[i], normKw,
SummaryNode::category_from_keyword(keywords[i]), category,
SummaryNode::Type::Undefined, SummaryNode::Type::Undefined,
wgnames[i], wgnames[i],
nums[i], nums[i],
@ -207,13 +238,16 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
} else { } else {
for (unsigned int i=0; i<keywords.size(); i++) { 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); combindKeyList.push_back(keyString);
if (! keyString.empty()) { if (! keyString.empty()) {
summaryNodes.push_back( { summaryNodes.push_back( {
keywords[i], normKw,
SummaryNode::category_from_keyword(keywords[i]), category,
SummaryNode::Type::Undefined, SummaryNode::Type::Undefined,
wgnames[i], wgnames[i],
nums[i], nums[i],
@ -317,14 +351,17 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (have_lgr) { if (have_lgr) {
for (size_t i = 0; i < keywords.size(); i++) { for (size_t i = 0; i < keywords.size(); i++) {
Opm::EclIO::lgr_info lgr { lgrs[i], {numlx[i], numly[i], numlz[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); combindKeyList.push_back(keyString);
if (! keyString.empty()) { if (! keyString.empty()) {
summaryNodes.push_back( { summaryNodes.push_back( {
keywords[i], normKw,
SummaryNode::category_from_keyword(keywords[i]), category,
SummaryNode::Type::Undefined, SummaryNode::Type::Undefined,
wgnames[i], wgnames[i],
nums[i], nums[i],
@ -341,13 +378,16 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
for (size_t i = 0; i < keywords.size(); i++) { 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); combindKeyList.push_back(keyString);
if (! keyString.empty()) { if (! keyString.empty()) {
summaryNodes.push_back( { summaryNodes.push_back( {
keywords[i], normKw,
SummaryNode::category_from_keyword(keywords[i]), category,
SummaryNode::Type::Undefined, SummaryNode::Type::Undefined,
wgnames[i], wgnames[i],
nums[i], nums[i],
@ -425,13 +465,16 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (have_lgr) { if (have_lgr) {
for (size_t i=0; i < keywords.size(); i++) { for (size_t i=0; i < keywords.size(); i++) {
Opm::EclIO::lgr_info lgr { lgrs[i], {numlx[i], numly[i], numlz[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())) if ((!keyw.empty()) && (keywList.find(keyw) != keywList.end()))
arrayPos[specInd][keyIndex[keyw]]=i; arrayPos[specInd][keyIndex[keyw]]=i;
} }
} else { } else {
for (size_t i=0; i < keywords.size(); i++) { 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())) if ((!keyw.empty()) && (keywList.find(keyw) != keywList.end()))
arrayPos[specInd][keyIndex[keyw]]=i; arrayPos[specInd][keyIndex[keyw]]=i;
} }
@ -1251,6 +1294,10 @@ std::string ESmry::makeKeyString(const std::string& keywordArg, const std::strin
return ""; return "";
} }
if (is_well_completion(keywordArg)) {
return fmt::format("{}:{}:{}", keywordArg, wgname, num);
}
return fmt::format("{}:{}", keywordArg, wgname); 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 std::string ESmry::unpackNumber(const SummaryNode& node) const
{ {
if (node.category == SummaryNode::Category::Block || if ((node.category == SummaryNode::Category::Block) ||
node.category == SummaryNode::Category::Connection) { (node.category == SummaryNode::Category::Connection) ||
((node.category == SummaryNode::Category::Completion) &&
is_connection_completion(node.keyword)))
{
int _i,_j,_k; int _i,_j,_k;
ijk_from_global_index(node.number, _i, _j, _k); ijk_from_global_index(node.number, _i, _j, _k);
return fmt::format("{},{},{}", _i, _j, _k); return fmt::format("{},{},{}", _i, _j, _k);
} }
else if (node.category == SummaryNode::Category::Region && node.keyword[2] == 'F') { else if ((node.category == SummaryNode::Category::Region) &&
const auto r1 = node.number % (1 << 15); (node.keyword[2] == 'F'))
const auto r2 = (node.number / (1 << 15)) - 10; {
const auto& [r1, r2] = splitSummaryNumber(node.number);
return fmt::format("{}-{}", r1, r2); 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::Aquifer: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Block: [[fallthrough]]; case Opm::EclIO::SummaryNode::Category::Block: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Connection: [[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::Region: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Segment: case Opm::EclIO::SummaryNode::Category::Segment:
return true; return true;
@ -50,6 +51,7 @@ constexpr bool use_number(Opm::EclIO::SummaryNode::Category category) {
constexpr bool use_name(Opm::EclIO::SummaryNode::Category category) { constexpr bool use_name(Opm::EclIO::SummaryNode::Category category) {
switch (category) { switch (category) {
case Opm::EclIO::SummaryNode::Category::Connection: [[fallthrough]]; 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::Group: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Segment: [[fallthrough]]; case Opm::EclIO::SummaryNode::Category::Segment: [[fallthrough]];
case Opm::EclIO::SummaryNode::Category::Node: [[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(); 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 Opm::EclIO::SummaryNode::Category
distinguish_group_from_node(const std::string& keyword) 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::Node
: Opm::EclIO::SummaryNode::Category::Group; : 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 { Opm::EclIO::SummaryNode::Category
std::vector<std::string> key_parts { keyword } ; 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()) if (auto opt = display_name())
key_parts.emplace_back(opt.value()); key_parts.emplace_back(opt.value());
@ -146,43 +199,50 @@ bool Opm::EclIO::SummaryNode::is_user_defined() const {
return matched && !blacklisted; return matched && !blacklisted;
} }
/* /*
Observe that this function started out as a slight generalisation of the Observe that this function started out as a slight generalisation of the
special case handling of segment variables; i.e. variables starting with 'S'. 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 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. 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"}; static const std::unordered_set<std::string> miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"};
return miscellaneous_keywords.count(keyword) == 1; return miscellaneous_keywords.count(keyword) == 1;
} }
Opm::EclIO::SummaryNode::Category
Opm::EclIO::SummaryNode::Category Opm::EclIO::SummaryNode::category_from_keyword( Opm::EclIO::SummaryNode::category_from_keyword(const std::string& keyword)
const std::string& keyword {
) { if (keyword.empty() ||
static const std::unordered_set<std::string> miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"}; Opm::EclIO::SummaryNode::miscellaneous_exception(keyword))
if (keyword.length() == 0) { {
return Category::Miscellaneous; return Category::Miscellaneous;
} }
if (Opm::EclIO::SummaryNode::miscellaneous_exception(keyword))
return Category::Miscellaneous;
switch (keyword[0]) { switch (keyword[0]) {
case 'A': return Category::Aquifer; case 'A': return Category::Aquifer;
case 'B': return Category::Block; case 'B': return Category::Block;
case 'C': return Category::Connection; case 'C': return distinguish_connection_from_completion(keyword);
case 'F': return Category::Field; case 'F': return Category::Field;
case 'G': return distinguish_group_from_node(keyword); case 'G': return distinguish_group_from_node(keyword);
case 'R': return Category::Region; case 'R': return Category::Region;
case 'S': return Category::Segment; case 'S': return Category::Segment;
case 'W': return Category::Well; case 'W': return distinguish_well_from_completion(keyword);
default: return Category::Miscellaneous; 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 { std::optional<std::string> Opm::EclIO::SummaryNode::display_name() const {
if (use_name(category)) { if (use_name(category)) {
return wgname; 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::Region: name = "Region"; break;
case Category::Block: name = "Block"; break; case Category::Block: name = "Block"; break;
case Category::Connection: name = "Connection"; break; case Category::Connection: name = "Connection"; break;
case Category::Completion: name = "Completion"; break;
case Category::Segment: name = "Segment"; break; case Category::Segment: name = "Segment"; break;
case Category::Aquifer: name = "Aquifer"; break; case Category::Aquifer: name = "Aquifer"; break;
case Category::Node: name = "Node"; 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 // 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 // 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 // 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; const size_t global_index = args.num - 1;
if (args.schedule_wells.empty()) if (args.schedule_wells.empty())
return zero; 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 // 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 // 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 // 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; const size_t global_index = args.num - 1;
if (args.schedule_wells.empty()) if (args.schedule_wells.empty())
return zero; 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 // 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 // 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 // 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 global_index = static_cast<std::size_t>(args.num - 1);
const auto& well_data = xwPos->second; 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 // 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 // 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 // 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()) { if (args.schedule_wells.empty()) {
return zero; return zero;
} }
@ -1049,7 +1050,7 @@ inline quantity trans_factors ( const fn_args& args ) {
// No dynamic results for this well. Not open? // No dynamic results for this well. Not open?
return zero; 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 size_t global_index = args.num - 1;
const auto& connections = xwPos->second.connections; const auto& connections = xwPos->second.connections;
auto connPos = std::find_if(connections.begin(), connections.end(), auto connPos = std::find_if(connections.begin(), connections.end(),
@ -1082,7 +1083,7 @@ inline quantity segpress ( const fn_args& args )
return zero; 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 size_t segNumber = args.num;
const auto& well_data = xwPos->second; 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 // 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 // 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 // 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 global_index = static_cast<std::size_t>(args.num) - 1;
const auto& xcon = xwPos->second.connections; const auto& xcon = xwPos->second.connections;
@ -2429,6 +2430,7 @@ find_wells(const Opm::Schedule& schedule,
switch (node.category) { switch (node.category) {
case Opm::EclIO::SummaryNode::Category::Well: case Opm::EclIO::SummaryNode::Category::Well:
case Opm::EclIO::SummaryNode::Category::Connection: case Opm::EclIO::SummaryNode::Category::Connection:
case Opm::EclIO::SummaryNode::Category::Completion:
case Opm::EclIO::SummaryNode::Category::Segment: case Opm::EclIO::SummaryNode::Category::Segment:
return find_single_well(schedule, node.wgname, sim_step); return find_single_well(schedule, node.wgname, sim_step);
@ -2450,7 +2452,7 @@ find_wells(const Opm::Schedule& schedule,
throw std::runtime_error { throw std::runtime_error {
fmt::format("Unhandled summary node category \"{}\" in find_wells()", 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) { switch (node.category) {
case Cat::Connection: [[fallthrough]]; case Cat::Connection: [[fallthrough]];
case Cat::Completion: [[fallthrough]];
case Cat::Field: [[fallthrough]]; case Cat::Field: [[fallthrough]];
case Cat::Group: [[fallthrough]]; case Cat::Group: [[fallthrough]];
case Cat::Segment: [[fallthrough]]; case Cat::Segment: [[fallthrough]];
@ -2484,7 +2487,10 @@ bool need_wells(const Opm::EclIO::SummaryNode& node)
return false; 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) void updateValue(const Opm::EclIO::SummaryNode& node, const double value, Opm::SummaryState& st)
@ -3396,7 +3402,10 @@ namespace Evaluator {
bool Factory::isFunctionRelation() 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()) { if (pos != funs.end()) {
// 'node_' represents a functional relation. // 'node_' represents a functional relation.
// Capture evaluation function and return true. // Capture evaluation function and return true.
@ -3404,41 +3413,45 @@ namespace Evaluator {
return true; return true;
} }
auto keyword = this->node_->keyword; if (normKw.length() <= std::string::size_type{4}) {
auto dash_pos = keyword.find("_"); return false;
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 (keyword.length() > 4 ) { const auto tracer_name = normKw.substr(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";
pos = funs.find(tracer_tag); const auto& tracers = this->es_.tracer();
if (pos != funs.end()) { auto trPos = std::find_if(tracers.begin(), tracers.end(),
this->paramFunction_ = pos->second; [&tracer_name](const auto& tracer)
return true; {
} 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; return false;

View File

@ -445,21 +445,12 @@ double ecl_sum_get_general_var(const EclIO::ESmry* smry,
return smry->get(var)[timeIdx]; 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, double ecl_sum_get_well_var( const EclIO::ESmry* smry,
const int timeIdx, const int timeIdx,
const std::string& wellname, const std::string& wellname,
const std::string& variable ) 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, 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& groupname,
const std::string& variable ) 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, 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 j,
const int k) const int k)
{ {
const auto ijk = std::to_string(i) + ',' + std::to_string(j) + ',' + std::to_string(k); return smry->get(fmt::format("{}:{}:{},{},{}", variable, wellname, i, j, k))[timeIdx];
return smry->get(variable + ':' + wellname + ':' + ijk)[timeIdx];
} }
bool ecl_sum_has_well_connection_var( const EclIO::ESmry* smry, 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 j,
const int k) 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); return ecl_sum_has_key(smry, key);
} }
@ -497,7 +496,6 @@ struct setup {
Deck deck; Deck deck;
EclipseState es; EclipseState es;
const EclipseGrid& grid; const EclipseGrid& grid;
std::shared_ptr<Python> python;
Schedule schedule; Schedule schedule;
SummaryConfig config; SummaryConfig config;
data::Wells wells; data::Wells wells;
@ -511,8 +509,7 @@ struct setup {
deck( Parser().parseFile( path) ), deck( Parser().parseFile( path) ),
es( deck ), es( deck ),
grid( es.getInputGrid() ), grid( es.getInputGrid() ),
python( std::make_shared<Python>() ), schedule( deck, es, std::make_shared<Python>()),
schedule( deck, es, python),
config( deck, schedule, es.fieldProps(), es.aquifer()), config( deck, schedule, es.fieldProps(), es.aquifer()),
wells( result_wells(w3_injector) ), wells( result_wells(w3_injector) ),
grp_nwrk( result_group_nwrk() ), grp_nwrk( result_group_nwrk() ),
@ -520,6 +517,7 @@ struct setup {
ta( "summary_test" ) ta( "summary_test" )
{} {}
}; };
} // Anonymous namespace } // Anonymous namespace
BOOST_AUTO_TEST_SUITE(Summary) 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( 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( 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_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_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_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); 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_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_var(resp, 1, "W_2", "WOPRL__2"), ecl_sum_get_well_var(resp, 1, "W_2", "WOFRL__2")); 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) + 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); 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) + 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 <opm/io/eclipse/SummaryNode.hpp>
#include <initializer_list>
#include <stdexcept>
#include <string>
#include <vector>
namespace { namespace {
void expect_key(const Opm::EclIO::SummaryNode& node, const std::string& unique_key) { void expect_key(const Opm::EclIO::SummaryNode& node, const std::string& unique_key) {
BOOST_CHECK_EQUAL(node.unique_key(), unique_key); BOOST_CHECK_EQUAL(node.unique_key(), unique_key);
} }
} } // Anonymous namespace
BOOST_AUTO_TEST_SUITE(UniqueKey) 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_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