Merge pull request #2853 from joakim-hove/wstats

WSTAT
This commit is contained in:
Joakim Hove 2021-11-22 09:50:04 +01:00 committed by GitHub
commit 6d669c9b6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 195 additions and 29 deletions

View File

@ -963,6 +963,7 @@ if(ENABLE_ECL_OUTPUT)
opm/output/eclipse/Summary.hpp
opm/output/eclipse/Tables.hpp
opm/output/eclipse/UDQDims.hpp
opm/output/eclipse/WStat.hpp
opm/output/eclipse/WindowedArray.hpp
opm/output/eclipse/WriteInit.hpp
opm/output/eclipse/WriteRFT.hpp

View File

@ -77,7 +77,11 @@ struct SummaryNode {
bool is_user_defined() const;
static Category category_from_keyword(const std::string&, const std::unordered_set<std::string> &miscellaneous_keywords = {});
static Category category_from_keyword(const std::string&);
// 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);
std::optional<std::string> display_name() const;
std::optional<std::string> display_number() const;

View File

@ -0,0 +1,58 @@
/*
Copyright 2021 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WSTAT_HPP
#define WSTAT_HPP
#include <string>
namespace Opm {
namespace WStat {
namespace numeric {
constexpr int UNKNOWN = 0;
constexpr int PROD = 1;
constexpr int INJ = 2;
constexpr int SHUT = 3;
constexpr int STOP = 4;
constexpr int PSHUT = 5;
constexpr int PSTOP = 6;
}
namespace symbolic {
const std::string UNKNOWN = "UNKNOWN";
const std::string PROD = "PROD";
const std::string INJ = "INJ";
const std::string SHUT = "SHUT";
const std::string STOP = "STOP";
const std::string PSHUT = "PSHUT";
const std::string PSTOP = "PSTOP";
}
}
}
#endif

View File

@ -31,6 +31,7 @@
#include <opm/common/utility/String.hpp>
#include <opm/common/utility/numeric/cmp.hpp>
#include <opm/common/utility/TimeService.hpp>
#include <opm/output/eclipse/WStat.hpp>
namespace Opm {
namespace EclIO {
@ -140,6 +141,20 @@ std::vector<double> make_multiplier(std::deque<std::string>& lines) {
return multiplier;
}
double convert_wstat(const std::string& symbolic_wstat) {
static const std::unordered_map<std::string, int> wstat_map = {
{Opm::WStat::symbolic::UNKNOWN, Opm::WStat::numeric::UNKNOWN},
{Opm::WStat::symbolic::PROD, Opm::WStat::numeric::PROD},
{Opm::WStat::symbolic::INJ, Opm::WStat::numeric::INJ},
{Opm::WStat::symbolic::SHUT, Opm::WStat::numeric::SHUT},
{Opm::WStat::symbolic::STOP, Opm::WStat::numeric::STOP},
{Opm::WStat::symbolic::PSHUT, Opm::WStat::numeric::PSHUT},
{Opm::WStat::symbolic::PSTOP, Opm::WStat::numeric::PSTOP},
};
return static_cast<double>(wstat_map.at(symbolic_wstat));
}
}
void ERsm::load_block(std::deque<std::string>& lines, std::size_t& vector_length) {
@ -194,7 +209,12 @@ void ERsm::load_block(std::deque<std::string>& lines, std::size_t& vector_length
auto data_row = split_line(pop_return(lines));
for (std::size_t data_index = 0; data_index < num_rows; data_index++) {
double value = std::stod(data_row[data_index + 1]) * mult_list[data_index + 1];
const auto& keyword = kw_list[data_index + 1];
double value;
if (keyword == "WSTAT")
value = convert_wstat(data_row[data_index + 1]);
else
value = std::stod(data_row[data_index + 1]) * mult_list[data_index + 1];
block_data[data_index].data.push_back(value);
}

View File

@ -125,12 +125,6 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
std::set<std::string> keywList;
std::vector<std::pair<std::string,int>> smryArray;
const std::unordered_set<std::string> segmentExceptions {
"SEPARATE",
"STEPTYPE",
"SUMTHIN",
} ;
std::vector<EclFile> smspecList;
std::vector<std::string> vectList = {"DIMENS", "RESTART", "KEYWORDS", "NUMS", "UNITS"};
@ -194,7 +188,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i], segmentExceptions),
SummaryNode::category_from_keyword(keywords[i]),
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -215,7 +209,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i], segmentExceptions),
SummaryNode::category_from_keyword(keywords[i]),
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -326,7 +320,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i], segmentExceptions),
SummaryNode::category_from_keyword(keywords[i]),
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -349,7 +343,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) :
if (! keyString.empty()) {
summaryNodes.push_back( {
keywords[i],
SummaryNode::category_from_keyword(keywords[i], segmentExceptions),
SummaryNode::category_from_keyword(keywords[i]),
SummaryNode::Type::Undefined,
wgnames[i],
nums[i],
@ -1218,10 +1212,7 @@ std::string ESmry::makeKeyString(const std::string& keywordArg, const std::strin
}
if (first == 'S') {
const std::vector<std::string> segmExcep= {"STEPTYPE", "SEPARATE", "SUMTHIN"};
auto it = std::find(segmExcep.begin(), segmExcep.end(), keywordArg);
if (it != segmExcep.end()) {
if (SummaryNode::miscellaneous_exception(keywordArg)) {
return keywordArg;
}

View File

@ -31,9 +31,12 @@
#include <regex>
#include <string>
#include <fmt/format.h>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/utility/TimeService.hpp>
#include <opm/io/eclipse/ESmry.hpp>
#include <opm/output/eclipse/WStat.hpp>
#include "project-version.h"
@ -111,12 +114,33 @@ namespace {
os << '\n';
}
void write_data_row(std::ostream& os, const std::vector<std::string>& time_column, const std::vector<std::pair<std::vector<float>, int>>& data, std::size_t index, char prefix = ' ') {
std::string convert_wstat(double numeric_wstat) {
static const std::unordered_map<int, std::string> wstat_map = {
{Opm::WStat::numeric::UNKNOWN, Opm::WStat::symbolic::UNKNOWN},
{Opm::WStat::numeric::PROD, Opm::WStat::symbolic::PROD},
{Opm::WStat::numeric::INJ, Opm::WStat::symbolic::INJ},
{Opm::WStat::numeric::SHUT, Opm::WStat::symbolic::SHUT},
{Opm::WStat::numeric::STOP, Opm::WStat::symbolic::STOP},
{Opm::WStat::numeric::PSHUT, Opm::WStat::symbolic::PSHUT},
{Opm::WStat::numeric::PSTOP, Opm::WStat::symbolic::PSTOP},
};
return wstat_map.at(static_cast<int>(numeric_wstat));
}
void write_data_row(std::ostream& os, const std::vector<std::string>& time_column, const std::vector<Opm::EclIO::SummaryNode>& summary_nodes, const std::vector<std::pair<std::vector<float>, int>>& data, std::size_t time_index, char prefix = ' ') {
os << prefix;
print_time_element( os, time_column[index] );
for (const auto& vector : data) {
print_float_element(os, vector.first[index] * std::pow(10.0, -vector.second));
print_time_element( os, time_column[time_index] );
for (std::size_t row_index = 0; row_index < data.size(); row_index++) {
const auto& [time_series, scale_factor] = data[row_index];
const auto& summary_node = summary_nodes[row_index];
if (summary_node.keyword == "WSTAT")
print_text_element(os, convert_wstat(time_series[time_index]));
else
print_float_element(os, time_series[time_index] * std::pow(10.0, -scale_factor));
}
write_padding(os, data.size());
@ -127,8 +151,8 @@ namespace {
os << prefix;
print_text_element(os, "");
for (const auto& vector : data) {
const auto scale_factor { vector.second } ;
for (const auto& [_ , scale_factor] : data) {
(void)_;
if (scale_factor) {
print_text_element(os, "*10**" + std::to_string(scale_factor));
} else {
@ -155,7 +179,7 @@ void ESmry::write_block(std::ostream& os, bool write_dates, const std::vector<st
bool has_scale_factors { false } ;
for (const auto& vector : vectors) {
const auto& vector_data { get(vector) } ;
const auto& vector_data { this->get(vector) } ;
auto max = *std::max_element(vector_data.begin(), vector_data.end());
int scale_factor { std::max(0, 3 * static_cast<int>(std::floor(( std::log10(max) - 4 ) / 3 ))) } ;
@ -186,7 +210,7 @@ void ESmry::write_block(std::ostream& os, bool write_dates, const std::vector<st
write_line(os, divider_line);
for (std::size_t i { 0 } ; i < rows; i++) {
write_data_row(os, time_column, data, i);
write_data_row(os, time_column, vectors, data, i);
}
}

View File

@ -146,17 +146,29 @@ 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) {
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,
const std::unordered_set<std::string>& miscellaneous_keywords
const std::string& keyword
) {
static const std::unordered_set<std::string> miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"};
if (keyword.length() == 0) {
return Category::Miscellaneous;
}
if (miscellaneous_keywords.find(keyword) != miscellaneous_keywords.end()) {
if (Opm::EclIO::SummaryNode::miscellaneous_exception(keyword))
return Category::Miscellaneous;
}
switch (keyword[0]) {
case 'A': return Category::Aquifer;

View File

@ -20,6 +20,7 @@
*/
#include <opm/output/eclipse/Summary.hpp>
#include <opm/output/eclipse/WStat.hpp>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/OpmLog/KeywordLocation.hpp>
@ -1064,6 +1065,25 @@ inline quantity segpress ( const fn_args& args )
return { segment->second.pressures[ix], measure::pressure };
}
inline quantity wstat( const fn_args& args ) {
const quantity zero = { Opm::WStat::numeric::UNKNOWN, measure::identity};
if (args.schedule_wells.empty())
return zero;
const auto& sched_well = args.schedule_wells.front();
const auto& arg_well = args.wells.find(sched_well->name());
if (arg_well == args.wells.end() || arg_well->second.dynamicStatus == Opm::Well::Status::SHUT)
return {Opm::WStat::numeric::SHUT, measure::identity};
if (arg_well->second.dynamicStatus == Opm::Well::Status::STOP)
return {Opm::WStat::numeric::STOP, measure::identity};
if (sched_well->isInjector())
return {Opm::WStat::numeric::INJ, measure::identity};
return {Opm::WStat::numeric::PROD, measure::identity};
}
inline quantity bhp( const fn_args& args ) {
const quantity zero = { 0, measure::pressure };
if (args.schedule_wells.empty())
@ -1676,6 +1696,7 @@ static const std::unordered_map< std::string, ofun > funs = {
{ "WGLR", div( rate< rt::gas, producer >,
sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) },
{ "WSTAT", wstat },
{ "WBHP", bhp },
{ "WTHP", thp },
{ "WTPCHEA", temperature< producer >},

View File

@ -20,7 +20,7 @@
#define BOOST_TEST_MODULE SummaryConfigTests
#include <boost/test/unit_test.hpp>
#include <fmt/format.h>
#include <opm/common/utility/OpmInputError.hpp>
#include <opm/io/eclipse/SummaryNode.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
@ -209,6 +209,16 @@ BOOST_AUTO_TEST_CASE(wells_all) {
names.begin(), names.end() );
}
BOOST_AUTO_TEST_CASE(WSTATE) {
const auto input = "WSTAT\n/\n";
const auto summary = createSummary( input );
const auto wells = { "PRODUCER", "WX2", "W_1", "W_3" };
for (const auto& well : wells)
BOOST_CHECK(summary.hasSummaryKey(fmt::format("WSTAT:{}", well)));
}
BOOST_AUTO_TEST_CASE(EMPTY) {
auto deck = createDeck_no_wells( "" );
auto python = std::make_shared<Python>();

View File

@ -748,6 +748,9 @@ COPRL
W_2 /
/
WSTAT
/
-- Water injection per connection
CWIR

View File

@ -29,12 +29,14 @@
#include <unordered_map>
#include <cctype>
#include <ctime>
#include <filesystem>
#include <fmt/format.h>
#include <opm/output/data/Groups.hpp>
#include <opm/output/data/GuideRateValue.hpp>
#include <opm/output/data/Wells.hpp>
#include <opm/output/eclipse/WStat.hpp>
#include <opm/output/eclipse/Summary.hpp>
#include <opm/common/utility/TimeService.hpp>
@ -53,6 +55,7 @@
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/io/eclipse/ESmry.hpp>
#include <opm/io/eclipse/ERsm.hpp>
#include <tests/WorkArea.cpp>
@ -791,6 +794,11 @@ BOOST_AUTO_TEST_CASE(well_keywords) {
BOOST_CHECK_CLOSE( 0.2, ecl_sum_get_well_var( resp, 1, "W_1", "WTHPH" ), 1e-5 );
BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 1, "W_2", "WTHPH" ), 1e-5 );
BOOST_CHECK_CLOSE( 2.2, ecl_sum_get_well_var( resp, 1, "W_3", "WTHPH" ), 1e-5 );
/* State */
BOOST_CHECK_CLOSE( WStat::numeric::PROD, ecl_sum_get_well_var(resp, 1,"W_1", "WSTAT"), 1e-5 );
BOOST_CHECK_CLOSE( WStat::numeric::PROD, ecl_sum_get_well_var(resp, 1,"W_2", "WSTAT"), 1e-5 );
BOOST_CHECK_CLOSE( WStat::numeric::INJ, ecl_sum_get_well_var(resp, 1,"W_3", "WSTAT"), 1e-5 );
}
BOOST_AUTO_TEST_CASE(well_keywords_dynamic_close) {
@ -819,6 +827,10 @@ BOOST_AUTO_TEST_CASE(well_keywords_dynamic_close) {
auto res = readsum( cfg.name );
const auto* resp = res.get();
/* State */
BOOST_CHECK_CLOSE( WStat::numeric::SHUT, ecl_sum_get_well_var(resp, 1,"W_2", "WSTAT"), 1e-5 );
BOOST_CHECK_CLOSE( WStat::numeric::PROD, ecl_sum_get_well_var(resp, 2,"W_2", "WSTAT"), 1e-5 );
/* Production rates */
BOOST_CHECK_CLOSE( 0.0, ecl_sum_get_well_var( resp, 1, "W_2", "WWPR" ), 1e-5 );
BOOST_CHECK_CLOSE( 0.0, ecl_sum_get_well_var( resp, 1, "W_2", "WTPRSEA" ), 1e-5 );
@ -917,6 +929,16 @@ BOOST_AUTO_TEST_CASE(well_keywords_dynamic_close) {
BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 0, "W_2", "WTHPH" ), 1e-5 );
BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 1, "W_2", "WTHPH" ), 1e-5 );
BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 2, "W_2", "WTHPH" ), 1e-5 );
// Dump summary object as RSM file, load the new RSM file and compare.
{
std::string rsm_file = "TEST.RSM";
std::filesystem::path rsm_path{rsm_file};
resp->write_rsm_file(rsm_path);
Opm::EclIO::ERsm rsm(rsm_file);
BOOST_CHECK(cmp(*resp, rsm));
}
}
BOOST_AUTO_TEST_CASE(udq_keywords) {