From 2eb3a06024c390132cbdf138d7f74599ffbc6517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Wed, 9 Oct 2019 00:25:45 -0500 Subject: [PATCH] Replace Writer for Summary/SMSPEC Files This commit replaces the existing system for writing summary and specification (SMSPEC) files with a new implementation based on class EclOutput. We package the evaluators of individual parameters in a set of classes determined by the parameter's category which each implement a virtual 'update()' function. This update function ultimately writes new values into a SummaryState object. Add a factory-like system for instantiating the appropriate class depending on a SummaryNode's 'category()'. Also, add a helper class for managing the parameters that a configured in a simulation model's SUMMARY section in order to distinguish these from those parameters that are merely needed for restart purposes. The summary class's 'eval()' function then becomes a loop over the evaluators for parameters in SUMMARY followed by a loop over the evaluators for restart vectors. We reimplement the 'internal_store()' function in terms of an std::vector of a helper structure 'MiniStep' which holds a ministep ID (contiguous counter started at zero), a report step ID, and all the evaluated parameters of this ministep. The final write function then consists of outputting those ministep structures that have accumulated since the previous call to write(). If a simulation does not call write at all, then this will accumulate all parameters for all ministeps throughout the simulation history. We create the SMSPEC file at most once, and write to it at most each report step. We create the summary file once (if unified) or at each report step (if separate). --- opm/output/eclipse/Summary.hpp | 76 +- src/opm/output/eclipse/Summary.cpp | 1713 ++++++++++++++++++++-------- 2 files changed, 1273 insertions(+), 516 deletions(-) diff --git a/opm/output/eclipse/Summary.hpp b/opm/output/eclipse/Summary.hpp index 6db1462b1..d56ef5627 100644 --- a/opm/output/eclipse/Summary.hpp +++ b/opm/output/eclipse/Summary.hpp @@ -21,66 +21,58 @@ #define OPM_OUTPUT_SUMMARY_HPP #include +#include #include +#include #include -#include -#include - -#include -#include - -#include -#include - namespace Opm { - + class EclipseGrid; class EclipseState; class Schedule; class SummaryConfig; - class EclipseGrid; - class Schedule; + class SummaryState; +} // namespace Opm -namespace out { +namespace Opm { namespace data { + class WellRates; +}} // namespace Opm::data + +namespace Opm { namespace out { class Summary { public: - Summary( const EclipseState&, const SummaryConfig&, const EclipseGrid&, const Schedule& ); - Summary( const EclipseState&, const SummaryConfig&, const EclipseGrid&, const Schedule&, const std::string& ); - Summary( const EclipseState&, const SummaryConfig&, const EclipseGrid&, const Schedule&, const char* basename ); - - void add_timestep(const SummaryState& st, - int report_step); - - - void eval(SummaryState& summary_state, - int report_step, - double secs_elapsed, - const EclipseState& es, - const Schedule& schedule, - const data::Wells&, - const std::map& single_values, - const std::map>& region_values = {}, - const std::map, double>& block_values = {}) const; - + using GlobalProcessParameters = std::map; + using RegionParameters = std::map>; + using BlockValues = std::map, double>; + Summary(const EclipseState& es, + const SummaryConfig& sumcfg, + const EclipseGrid& grid, + const Schedule& sched, + const std::string& basename = ""); ~Summary(); + + void add_timestep(const SummaryState& st, const int report_step); + + void eval(SummaryState& summary_state, + const int report_step, + const double secs_elapsed, + const EclipseState& es, + const Schedule& schedule, + const data::WellRates& well_solution, + const GlobalProcessParameters& single_values, + const RegionParameters& region_values = {}, + const BlockValues& block_values = {}) const; + void write() const; private: - void internal_store(const SummaryState& summary_state, int report_step); - - - class keyword_handlers; - - const EclipseGrid& grid; - out::RegionCache regionCache; - ERT::ert_unique_ptr< ecl_sum_type, ecl_sum_free > ecl_sum; - std::unique_ptr< keyword_handlers > handlers; + class SummaryImplementation; + std::unique_ptr pImpl_; }; -} -} +}} // namespace Opm::out #endif //OPM_OUTPUT_SUMMARY_HPP diff --git a/src/opm/output/eclipse/Summary.cpp b/src/opm/output/eclipse/Summary.cpp index 27d6a2be1..17d1a8b91 100644 --- a/src/opm/output/eclipse/Summary.cpp +++ b/src/opm/output/eclipse/Summary.cpp @@ -1,4 +1,5 @@ /* + Copyright 2019 Equinor ASA. Copyright 2016 Statoil ASA. This file is part of the Open Porous Media project (OPM). @@ -17,120 +18,182 @@ along with OPM. If not, see . */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include +#include #include #include +#include #include #include #include -#include -#include #include #include -#include -#include #include - -#include -#include - +#include +#include #include #include +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace { - struct SegmentResultDescriptor + struct ParamCTorArgs { - std::string vector; - std::string well; - std::size_t segNumber; + std::string kw; + Opm::SummaryNode::Type type; }; - std::vector requiredRestartVectors() + std::vector requiredRestartVectors() { + using Type = ::Opm::SummaryNode::Type; + return { - "OPR", "WPR", "GPR", "VPR", - "OPT", "WPT", "GPT", "VPT", - "WIR", "GIR", - "WIT", "GIT", - "WCT", "GOR", - "OPTH", "WPTH", "GPTH", - "WITH", "GITH", - "OPP", "WPP", "GPP", - "OPI", "WPI", "GPI" + // Production + ParamCTorArgs{ "OPR" , Type::Rate }, + ParamCTorArgs{ "WPR" , Type::Rate }, + ParamCTorArgs{ "GPR" , Type::Rate }, + ParamCTorArgs{ "VPR" , Type::Rate }, + ParamCTorArgs{ "OPP" , Type::Rate }, + ParamCTorArgs{ "WPP" , Type::Rate }, + ParamCTorArgs{ "GPP" , Type::Rate }, + ParamCTorArgs{ "OPT" , Type::Total }, + ParamCTorArgs{ "WPT" , Type::Total }, + ParamCTorArgs{ "GPT" , Type::Total }, + ParamCTorArgs{ "VPT" , Type::Total }, + ParamCTorArgs{ "OPTH", Type::Total }, + ParamCTorArgs{ "WPTH", Type::Total }, + ParamCTorArgs{ "GPTH", Type::Total }, + + // Flow rate ratios (production) + ParamCTorArgs{ "WCT" , Type::Ratio }, + ParamCTorArgs{ "GOR" , Type::Ratio }, + + // injection + ParamCTorArgs{ "WIR" , Type::Rate }, + ParamCTorArgs{ "GIR" , Type::Rate }, + ParamCTorArgs{ "OPI" , Type::Rate }, + ParamCTorArgs{ "WPI" , Type::Rate }, + ParamCTorArgs{ "GPI" , Type::Rate }, + ParamCTorArgs{ "WIT" , Type::Total }, + ParamCTorArgs{ "GIT" , Type::Total }, + ParamCTorArgs{ "WITH", Type::Total }, + ParamCTorArgs{ "GITH", Type::Total }, }; } - std::vector> + std::vector requiredRestartVectors(const ::Opm::Schedule& sched) { - auto entities = std::vector>{}; + auto entities = std::vector{}; + + using SN = ::Opm::SummaryNode; const auto& vectors = requiredRestartVectors(); auto makeEntities = [&vectors, &entities] - (const char cat, const std::string& name) + (const char kwpref, + const SN::Category cat, + const std::string& name) -> void { for (const auto& vector : vectors) { - entities.emplace_back(cat + vector, name); + entities.emplace_back(kwpref + vector.kw, cat); + + entities.back().namedEntity(name) + .parameterType(vector.type); } }; for (const auto& well_name : sched.wellNames()) { - makeEntities('W', well_name); + makeEntities('W', SN::Category::Well, well_name); - entities.emplace_back("WBHP" , well_name); - entities.emplace_back("WGVIR", well_name); - entities.emplace_back("WWVIR", well_name); + entities.emplace_back("WBHP", SN::Category::Well); + entities.back().namedEntity(well_name) + .parameterType(SN::Type::Pressure); + + entities.emplace_back("WGVIR", SN::Category::Well); + entities.back().namedEntity(well_name) + .parameterType(SN::Type::Rate); + + entities.emplace_back("WWVIR", SN::Category::Well); + entities.back().namedEntity(well_name) + .parameterType(SN::Type::Rate); } for (const auto& grp_name : sched.groupNames()) { if (grp_name != "FIELD") - makeEntities('G', grp_name); + makeEntities('G', SN::Category::Group, grp_name); } - makeEntities('F', "FIELD"); + makeEntities('F', SN::Category::Field, "FIELD"); return entities; } - std::vector + std::vector requiredSegmentVectors(const ::Opm::Schedule& sched) { - using SRD = SegmentResultDescriptor; - auto ret = std::vector{}; + using SN = Opm::SummaryNode; + auto ret = std::vector{}; + + auto sofr = SN{ "SOFR", SN::Category::Segment } + .parameterType(SN::Type::Rate); + + auto sgfr = SN{ "SGFR", SN::Category::Segment } + .parameterType(SN::Type::Rate); + + auto swfr = SN{ "SWFR", SN::Category::Segment } + .parameterType(SN::Type::Rate); + + auto spr = SN{ "SPR", SN::Category::Segment } + .parameterType(SN::Type::Pressure); auto makeVectors = - [&ret](const std::string& well, - const std::size_t segNumber) -> void + [&](const std::string& well, + const int segNumber) -> void { - ret.push_back(SRD{"SOFR", well, segNumber}); - ret.push_back(SRD{"SGFR", well, segNumber}); - ret.push_back(SRD{"SWFR", well, segNumber}); - ret.push_back(SRD{"SPR" , well, segNumber}); + ret.push_back(sofr.namedEntity(well).number(segNumber)); + ret.push_back(sgfr.namedEntity(well).number(segNumber)); + ret.push_back(swfr.namedEntity(well).number(segNumber)); + ret.push_back(spr .namedEntity(well).number(segNumber)); }; - for (const auto& well : sched.getWells2atEnd()) { + for (const auto& wname : sched.wellNames()) { + const auto& well = sched.getWell2atEnd(wname); + if (! well.isMultiSegment()) { // Don't allocate MS summary vectors for non-MS wells. continue; } - const auto& wname = well.name(); - const auto nSeg = - well.getSegments().size(); + const auto nSeg = well.getSegments().size(); for (auto segID = 0*nSeg; segID < nSeg; ++segID) { makeVectors(wname, segID + 1); // One-based @@ -140,33 +203,6 @@ namespace { return ret; } - std::string genKey(const std::string& vector, - const std::string& entity) - { - return (entity == "FIELD") - ? vector - : vector + ':' + entity; - } - - std::string genKey(const SegmentResultDescriptor& segRes) - { - return segRes.vector + ':' + segRes.well + - ':' + std::to_string(segRes.segNumber); - } - - std::unique_ptr - makeRestartVectorSMSPEC(const std::string& vector, - const std::string& entity) - { - return std::unique_ptr( new ecl::smspec_node(0, vector.c_str(), entity.c_str(), "UNIT", 0.0f, ":")); - } - - std::unique_ptr - makeRestartVectorSMSPEC(const SegmentResultDescriptor& segRes) - { - return std::unique_ptr(new ecl::smspec_node(0, segRes.vector.c_str(), segRes.well.c_str(), static_cast(segRes.segNumber), "UNIT", 0.0f, ":")); - } - /* * This class takes simulator state and parser-provided information and * orchestrates ert to write simulation results as requested by the SUMMARY @@ -175,19 +211,6 @@ namespace { * the compiler writes a lot of this code for us. */ - void update(const ecl::smspec_node& node, const double value, Opm::SummaryState& st) - { - if (node.get_var_type() == ECL_SMSPEC_WELL_VAR) - st.update_well_var(node.get_wgname(), - node.get_keyword(), - value); - else if (node.get_var_type() == ECL_SMSPEC_GROUP_VAR) - st.update_group_var(node.get_wgname(), - node.get_keyword(), - value); - else - st.update(node.get_gen_key1(), value); - } using rt = Opm::data::Rates::opt; using measure = Opm::UnitSystem::measure; @@ -616,24 +639,6 @@ inline quantity res_vol_production_target( const fn_args& args ) { return { sum, measure::rate }; } -/* - * A small DSL, really poor man's function composition, to avoid massive - * repetition when declaring the handlers for each individual keyword. bin_op - * and its aliases will apply the pair of functions F and G that all take const - * fn_args& and return quantity, making them composable. - */ -template< typename F, typename G, typename Op > -struct bin_op { - bin_op( F fn, G gn = {} ) : f( fn ), g( gn ) {} - quantity operator()( const fn_args& args ) const { - return Op()( f( args ), g( args ) ); - } - - private: - F f; - G g; -}; - inline quantity duration( const fn_args& args ) { return { args.duration, measure::time }; } @@ -684,6 +689,24 @@ inline quantity potential_rate( const fn_args& args ) { return { sum, rate_unit< phase >() }; } +/* + * A small DSL, really poor man's function composition, to avoid massive + * repetition when declaring the handlers for each individual keyword. bin_op + * and its aliases will apply the pair of functions F and G that all take const + * fn_args& and return quantity, making them composable. + */ +template< typename F, typename G, typename Op > +struct bin_op { + bin_op( F fn, G gn = {} ) : f( fn ), g( gn ) {} + quantity operator()( const fn_args& args ) const { + return Op()( f( args ), g( args ) ); + } + + private: + F f; + G g; +}; + template< typename F, typename G > auto mul( F f, G g ) -> bin_op< F, G, std::multiplies< quantity > > { return { f, g }; } @@ -1086,17 +1109,18 @@ static const std::unordered_map< std::string, Opm::UnitSystem::measure> block_un }; inline std::vector find_wells( const Opm::Schedule& schedule, - const ecl::smspec_node* node, + const Opm::SummaryNode& node, const int sim_step, const Opm::out::RegionCache& regionCache ) { - const auto* name = smspec_node_get_wgname( node ); - const auto type = smspec_node_get_var_type( node ); + const auto cat = node.category(); - if ((type == ECL_SMSPEC_WELL_VAR) || - (type == ECL_SMSPEC_COMPLETION_VAR) || - (type == ECL_SMSPEC_SEGMENT_VAR)) + if ((cat == Opm::SummaryNode::Category::Well) || + (cat == Opm::SummaryNode::Category::Connection) || + (cat == Opm::SummaryNode::Category::Segment)) { + const auto& name = node.namedEntity(); + if (schedule.hasWell(name, sim_step)) { const auto& well = schedule.getWell2( name, sim_step ); return { well }; @@ -1104,19 +1128,21 @@ inline std::vector find_wells( const Opm::Schedule& schedule, return {}; } - if( type == ECL_SMSPEC_GROUP_VAR ) { + if( cat == Opm::SummaryNode::Category::Group ) { + const auto& name = node.namedEntity(); + if( !schedule.hasGroup( name ) ) return {}; return schedule.getChildWells2( name, sim_step); } - if( type == ECL_SMSPEC_FIELD_VAR ) + if( cat == Opm::SummaryNode::Category::Field ) return schedule.getWells2(sim_step); - if( type == ECL_SMSPEC_REGION_VAR ) { + if( cat == Opm::SummaryNode::Category::Region ) { std::vector wells; - const auto region = smspec_node_get_num( node ); + const auto region = node.number(); for ( const auto& connection : regionCache.connections( region ) ){ const auto& w_name = connection.first; @@ -1127,7 +1153,7 @@ inline std::vector find_wells( const Opm::Schedule& schedule, [&] ( const Opm::Well2& elem ) { return elem.name() == well.name(); }); if ( it == wells.end() ) - wells.push_back( schedule.getWell2( w_name, sim_step )); + wells.push_back( well ); } } @@ -1138,21 +1164,21 @@ inline std::vector find_wells( const Opm::Schedule& schedule, } -bool need_wells(ecl_smspec_var_type var_type, const std::string& keyword) { +bool need_wells(Opm::SummaryNode::Category cat, const std::string& keyword) { static const std::set region_keywords{"ROIR", "RGIR", "RWIR", "ROPR", "RGPR", "RWPR", "ROIT", "RWIT", "RGIT", "ROPT", "RGPT", "RWPT"}; - if (var_type == ECL_SMSPEC_WELL_VAR) + if (cat == Opm::SummaryNode::Category::Well) return true; - if (var_type == ECL_SMSPEC_GROUP_VAR) + if (cat == Opm::SummaryNode::Category::Group) return true; - if (var_type == ECL_SMSPEC_FIELD_VAR) + if (cat == Opm::SummaryNode::Category::Field) return true; - if (var_type == ECL_SMSPEC_COMPLETION_VAR) + if (cat == Opm::SummaryNode::Category::Connection) return true; - if (var_type == ECL_SMSPEC_SEGMENT_VAR) + if (cat == Opm::SummaryNode::Category::Segment) return true; /* @@ -1161,7 +1187,7 @@ bool need_wells(ecl_smspec_var_type var_type, const std::string& keyword) { region and consequently the list of defined wells is required, other region keywords like 'ROIP' do not require well information. */ - if (var_type == ECL_SMSPEC_REGION_VAR) { + if (cat == Opm::SummaryNode::Category::Region) { if (region_keywords.count(keyword) > 0) return true; } @@ -1169,7 +1195,6 @@ bool need_wells(ecl_smspec_var_type var_type, const std::string& keyword) { return false; } - void eval_udq(const Opm::Schedule& schedule, std::size_t sim_step, Opm::SummaryState& st) { using namespace Opm; @@ -1206,6 +1231,18 @@ void eval_udq(const Opm::Schedule& schedule, std::size_t sim_step, Opm::SummaryS } } +void updateValue(const Opm::SummaryNode& node, const double value, Opm::SummaryState& st) +{ + if (node.category() == Opm::SummaryNode::Category::Well) + st.update_well_var(node.namedEntity(), node.keyword(), value); + + else if (node.category() == Opm::SummaryNode::Category::Group) + st.update_group_var(node.namedEntity(), node.keyword(), value); + + else + st.update(node.uniqueNodeKey(), value); +} + /* * The well efficiency factor will not impact the well rate itself, but is * rather applied for accumulated values.The WEFAC can be considered to shut @@ -1222,23 +1259,37 @@ void eval_udq(const Opm::Schedule& schedule, std::size_t sim_step, Opm::SummaryS * rates and accumulated values. * */ -std::vector< std::pair< std::string, double > > -well_efficiency_factors( const ecl::smspec_node* node, - const Opm::Schedule& schedule, - const std::vector& schedule_wells, - const int sim_step ) { - std::vector< std::pair< std::string, double > > efac; +struct EfficiencyFactor +{ + using Factor = std::pair; + using FacColl = std::vector; - auto var_type = node->get_var_type(); - if( var_type != ECL_SMSPEC_GROUP_VAR - && var_type != ECL_SMSPEC_FIELD_VAR - && var_type != ECL_SMSPEC_REGION_VAR - && !node->is_total()) { - return efac; - } + FacColl factors{}; - const bool is_group = (var_type == ECL_SMSPEC_GROUP_VAR); - const bool is_rate = !node->is_total(); + void setFactors(const Opm::SummaryNode& node, + const Opm::Schedule& schedule, + const std::vector& schedule_wells, + const int sim_step); +}; + +void EfficiencyFactor::setFactors(const Opm::SummaryNode& node, + const Opm::Schedule& schedule, + const std::vector& schedule_wells, + const int sim_step) +{ + this->factors.clear(); + + if (schedule_wells.empty()) { return; } + + const auto cat = node.category(); + if( cat != Opm::SummaryNode::Category::Group + && cat != Opm::SummaryNode::Category::Field + && cat != Opm::SummaryNode::Category::Region + && (node.type() != Opm::SummaryNode::Type::Total)) + return; + + const bool is_group = (cat == Opm::SummaryNode::Category::Group); + const bool is_rate = (node.type() != Opm::SummaryNode::Type::Total); for( const auto& well : schedule_wells ) { if (!well.hasBeenDefined(sim_step)) @@ -1250,7 +1301,7 @@ well_efficiency_factors( const ecl::smspec_node* node, while(true){ if(( is_group && is_rate - && group_ptr->name() == node->get_wgname() )) + && group_ptr->name() == node.namedEntity() )) break; eff_factor *= group_ptr->getGroupEfficiencyFactor(); @@ -1258,239 +1309,1057 @@ well_efficiency_factors( const ecl::smspec_node* node, break; group_ptr = std::addressof( schedule.getGroup2( group_ptr->parent(), sim_step ) ); } - efac.emplace_back( well.name(), eff_factor ); + + this->factors.emplace_back( well.name(), eff_factor ); + } +} + +namespace Evaluator { + struct InputData + { + const Opm::EclipseState& es; + const Opm::Schedule& sched; + const Opm::EclipseGrid& grid; + const Opm::out::RegionCache& reg; + }; + + struct SimulatorResults + { + const Opm::data::WellRates& wellSol; + const std::map& single; + const std::map>& region; + const std::map, double>& block; + }; + + class Base + { + public: + virtual ~Base() {} + + virtual void update(const std::size_t sim_step, + const double stepSize, + const InputData& input, + const SimulatorResults& simRes, + Opm::SummaryState& st) const = 0; + }; + + class FunctionRelation : public Base + { + public: + explicit FunctionRelation(Opm::SummaryNode node, ofun fcn) + : node_(std::move(node)) + , fcn_ (std::move(fcn)) + {} + + void update(const std::size_t sim_step, + const double stepSize, + const InputData& input, + const SimulatorResults& simRes, + Opm::SummaryState& st) const override + { + const auto get_wells = + need_wells(this->node_.category(), this->node_.keyword()); + + const auto wells = get_wells + ? find_wells(input.sched, this->node_, + static_cast(sim_step), input.reg) + : std::vector{}; + + if (get_wells && wells.empty()) + // Parameter depends on well information, but no active + // wells apply at this sim_step. Nothing to do. + return; + + EfficiencyFactor efac{}; + efac.setFactors(this->node_, input.sched, wells, sim_step); + + const fn_args args { + wells, stepSize, static_cast(sim_step), + std::max(0, this->node_.number()), + st, simRes.wellSol, input.reg, input.grid, + std::move(efac.factors) + }; + + const auto& usys = input.es.getUnits(); + const auto prm = this->fcn_(args); + + updateValue(this->node_, usys.from_si(prm.unit, prm.value), st); + } + + private: + Opm::SummaryNode node_; + ofun fcn_; + }; + + class BlockValue : public Base + { + public: + explicit BlockValue(Opm::SummaryNode node, + const Opm::UnitSystem::measure m) + : node_(std::move(node)) + , m_ (m) + {} + + void update(const std::size_t /* sim_step */, + const double /* stepSize */, + const InputData& input, + const SimulatorResults& simRes, + Opm::SummaryState& st) const override + { + auto xPos = simRes.block.find(this->lookupKey()); + if (xPos == simRes.block.end()) { + return; + } + + const auto& usys = input.es.getUnits(); + updateValue(this->node_, usys.from_si(this->m_, xPos->second), st); + } + + private: + Opm::SummaryNode node_; + Opm::UnitSystem::measure m_; + + Opm::out::Summary::BlockValues::key_type lookupKey() const + { + return { this->node_.keyword(), this->node_.number() }; + } + }; + + class RegionValue : public Base + { + public: + explicit RegionValue(Opm::SummaryNode node, + const Opm::UnitSystem::measure m) + : node_(std::move(node)) + , m_ (m) + {} + + void update(const std::size_t /* sim_step */, + const double /* stepSize */, + const InputData& input, + const SimulatorResults& simRes, + Opm::SummaryState& st) const override + { + if (this->node_.number() < 0) + return; + + auto xPos = simRes.region.find(this->node_.keyword()); + if (xPos == simRes.region.end()) + return; + + const auto ix = this->index(); + if (ix >= xPos->second.size()) + return; + + const auto val = xPos->second[ix]; + const auto& usys = input.es.getUnits(); + + updateValue(this->node_, usys.from_si(this->m_, val), st); + } + + private: + Opm::SummaryNode node_; + Opm::UnitSystem::measure m_; + + std::vector::size_type index() const + { + return this->node_.number() - 1; + } + }; + + class GlobalProcessValue : public Base + { + public: + explicit GlobalProcessValue(Opm::SummaryNode node, + const Opm::UnitSystem::measure m) + : node_(std::move(node)) + , m_ (m) + {} + + void update(const std::size_t /* sim_step */, + const double /* stepSize */, + const InputData& input, + const SimulatorResults& simRes, + Opm::SummaryState& st) const override + { + auto xPos = simRes.single.find(this->node_.keyword()); + if (xPos == simRes.single.end()) + return; + + const auto val = xPos->second; + const auto& usys = input.es.getUnits(); + + updateValue(this->node_, usys.from_si(this->m_, val), st); + } + + private: + Opm::SummaryNode node_; + Opm::UnitSystem::measure m_; + }; + + class UserDefinedValue : public Base + { + public: + void update(const std::size_t /* sim_step */, + const double /* stepSize */, + const InputData& /* input */, + const SimulatorResults& /* simRes */, + Opm::SummaryState& /* st */) const override + { + // No-op + } + }; + + class Time : public Base + { + public: + explicit Time(std::string saveKey) + : saveKey_(std::move(saveKey)) + {} + + void update(const std::size_t /* sim_step */, + const double stepSize, + const InputData& input, + const SimulatorResults& /* simRes */, + Opm::SummaryState& st) const override + { + const auto& usys = input.es.getUnits(); + + const auto m = ::Opm::UnitSystem::measure::time; + const auto val = st.get_elapsed() + stepSize; + + st.update(this->saveKey_, usys.from_si(m, val)); + } + + private: + std::string saveKey_; + }; + + class Years : public Base + { + public: + explicit Years(std::string saveKey) + : saveKey_(std::move(saveKey)) + {} + + void update(const std::size_t /* sim_step */, + const double stepSize, + const InputData& /* input */, + const SimulatorResults& /* simRes */, + Opm::SummaryState& st) const override + { + using namespace ::Opm::unit; + + const auto val = st.get_elapsed() + stepSize; + + st.update(this->saveKey_, convert::to(val, year)); + } + + private: + std::string saveKey_; + }; + + class Factory + { + public: + struct Descriptor + { + std::string uniquekey{}; + std::string unit{}; + std::unique_ptr evaluator{}; + }; + + explicit Factory(const Opm::EclipseState& es, + const Opm::EclipseGrid& grid, + const Opm::SummaryState& st, + const Opm::UDQConfig& udq) + : es_(es), grid_(grid), st_(st), udq_(udq) + {} + + ~Factory() = default; + + Factory(const Factory&) = delete; + Factory(Factory&&) = delete; + Factory& operator=(const Factory&) = delete; + Factory& operator=(Factory&&) = delete; + + Descriptor create(const Opm::SummaryNode&); + + private: + const Opm::EclipseState& es_; + const Opm::EclipseGrid& grid_; + const Opm::SummaryState& st_; + const Opm::UDQConfig& udq_; + + const Opm::SummaryNode* node_; + + Opm::UnitSystem::measure paramUnit_; + ofun paramFunction_; + + Descriptor functionRelation(); + Descriptor blockValue(); + Descriptor regionValue(); + Descriptor globalProcessValue(); + Descriptor userDefinedValue(); + Descriptor unknownParameter(); + + bool isBlockValue(); + bool isRegionValue(); + bool isGlobalProcessValue(); + bool isFunctionRelation(); + bool isUserDefined(); + + std::string functionUnitString() const; + std::string directUnitString() const; + std::string userDefinedUnit() const; + }; + + Factory::Descriptor Factory::create(const Opm::SummaryNode& node) + { + this->node_ = &node; + + if (this->isUserDefined()) + return this->userDefinedValue(); + + if (this->isBlockValue()) + return this->blockValue(); + + if (this->isRegionValue()) + return this->regionValue(); + + if (this->isGlobalProcessValue()) + return this->globalProcessValue(); + + if (this->isFunctionRelation()) + return this->functionRelation(); + + return this->unknownParameter(); } - return efac; -} + Factory::Descriptor Factory::functionRelation() + { + auto desc = this->unknownParameter(); + + desc.unit = this->functionUnitString(); + desc.evaluator.reset(new FunctionRelation { + *this->node_, std::move(this->paramFunction_) + }); + + return desc; + } + + Factory::Descriptor Factory::blockValue() + { + auto desc = this->unknownParameter(); + + desc.unit = this->directUnitString(); + desc.evaluator.reset(new BlockValue { + *this->node_, this->paramUnit_ + }); + + return desc; + } + + Factory::Descriptor Factory::regionValue() + { + auto desc = this->unknownParameter(); + + desc.unit = this->directUnitString(); + desc.evaluator.reset(new RegionValue { + *this->node_, this->paramUnit_ + }); + + return desc; + } + + Factory::Descriptor Factory::globalProcessValue() + { + auto desc = this->unknownParameter(); + + desc.unit = this->directUnitString(); + desc.evaluator.reset(new GlobalProcessValue { + *this->node_, this->paramUnit_ + }); + + return desc; + } + + Factory::Descriptor Factory::userDefinedValue() + { + auto desc = this->unknownParameter(); + + desc.unit = this->userDefinedUnit(); + desc.evaluator.reset(new UserDefinedValue {}); + + return desc; + } + + Factory::Descriptor Factory::unknownParameter() + { + auto desc = Descriptor{}; + + desc.uniquekey = this->node_->uniqueNodeKey(); + + return desc; + } + + bool Factory::isBlockValue() + { + auto pos = block_units.find(this->node_->keyword()); + if (pos == block_units.end()) + return false; + + if (! this->grid_.cellActive(this->node_->number() - 1)) + // 'node_' is a block value, but it is configured in a + // deactivated cell. Don't create an evaluation function. + return false; + + // 'node_' represents a block value in an active cell. + // Capture unit of measure and return true. + this->paramUnit_ = pos->second; + return true; + } + + bool Factory::isRegionValue() + { + auto pos = region_units.find(this->node_->keyword()); + if (pos == region_units.end()) + return false; + + // 'node_' represents a region value. Capture unit + // of measure and return true. + this->paramUnit_ = pos->second; + return true; + } + + bool Factory::isGlobalProcessValue() + { + auto pos = single_values_units.find(this->node_->keyword()); + if (pos == single_values_units.end()) + return false; + + // 'node_' represents a single value (i.e., global process) + // value. Capture unit of measure and return true. + this->paramUnit_ = pos->second; + return true; + } + + bool Factory::isFunctionRelation() + { + auto pos = funs.find(this->node_->keyword()); + if (pos == funs.end()) + return false; + + // 'node_' represents a functional relation. + // Capture evaluation function and return true. + this->paramFunction_ = pos->second; + return true; + } + + bool Factory::isUserDefined() + { + return this->node_->isUserDefined(); + } + + std::string Factory::functionUnitString() const + { + const auto reg = Opm::out::RegionCache{}; + + const fn_args args { + {}, 0.0, 0, std::max(0, this->node_->number()), + this->st_, {}, reg, this->grid_, + {} + }; + + const auto prm = this->paramFunction_(args); + + return this->es_.getUnits().name(prm.unit); + } + + std::string Factory::directUnitString() const + { + return this->es_.getUnits().name(this->paramUnit_); + } + + std::string Factory::userDefinedUnit() const + { + const auto& kw = this->node_->keyword(); + + return this->udq_.has_unit(kw) + ? this->udq_.unit(kw) : "?????"; + } +} // namespace Evaluator + +void reportUnsupportedKeywords(std::vector keywords) +{ + std::sort(keywords.begin(), keywords.end()); + auto uend = std::unique(keywords.begin(), keywords.end()); + + for (auto kw = keywords.begin(); kw != uend; ++kw) + ::Opm::OpmLog::info("Summary keyword '" + *kw + "' is unhandled"); } -namespace Opm { -namespace out { +std::string makeWGName(std::string name) +{ + // Use default WGNAME if 'name' is empty or consists + // exlusively of white-space (space and tab) characters. + // + // Use 'name' itself otherwise. -class Summary::keyword_handlers { - public: - using fn = ofun; - std::vector< std::pair< const ecl::smspec_node*, fn > > handlers; - std::map< std::string, const ecl::smspec_node* > single_value_nodes; - std::map< std::pair , const ecl::smspec_node* > region_nodes; - std::map< std::pair , const ecl::smspec_node* > block_nodes; + const auto use_dflt = name.empty() || + (name.find_first_not_of(" \t") == std::string::npos); - // Memory management for restart-related summary vectors - // that are not requested in SUMMARY section. - std::vector> rstvec_backing_store; + return use_dflt ? std::string(":+:+:+:+") : std::move(name); +} + +class SummaryOutputParameters +{ +public: + using EvalPtr = std::unique_ptr; + using SMSpecPrm = Opm::EclIO::OutputStream:: + SummarySpecification::Parameters; + + SummaryOutputParameters() = default; + ~SummaryOutputParameters() = default; + + SummaryOutputParameters(const SummaryOutputParameters& rhs) = delete; + SummaryOutputParameters(SummaryOutputParameters&& rhs) = default; + + SummaryOutputParameters& + operator=(const SummaryOutputParameters& rhs) = delete; + + SummaryOutputParameters& + operator=(SummaryOutputParameters&& rhs) = default; + + void makeParameter(std::string keyword, + std::string name, + const int num, + std::string unit, + EvalPtr evaluator) + { + this->smspec_.add(std::move(keyword), std::move(name), + std::max (num, 0), std::move(unit)); + + this->evaluators_.push_back(std::move(evaluator)); + } + + const SMSpecPrm& summarySpecification() const + { + return this->smspec_; + } + + const std::vector& getEvaluators() const + { + return this->evaluators_; + } + +private: + SMSpecPrm smspec_{}; + std::vector evaluators_{}; }; -Summary::Summary( const EclipseState& st, - const SummaryConfig& sum , - const EclipseGrid& grid_arg, - const Schedule& schedule) : - Summary( st, sum, grid_arg, schedule, st.getIOConfig().fullBasePath().c_str() ) -{} - -Summary::Summary( const EclipseState& st, - const SummaryConfig& sum, - const EclipseGrid& grid_arg, - const Schedule& schedule, - const std::string& basename ) : - Summary( st, sum, grid_arg, schedule, basename.c_str() ) -{} - -Summary::Summary( const EclipseState& st, - const SummaryConfig& sum, - const EclipseGrid& grid_arg, - const Schedule& schedule, - const char* basename ) : - grid( grid_arg ), - regionCache( st.get3DProperties( ) , grid_arg, schedule ), - handlers( new keyword_handlers() ) +class SMSpecStreamDeferredCreation { +private: + using Spec = ::Opm::EclIO::OutputStream::SummarySpecification; - using Cat = SummaryNode::Category; +public: + using ResultSet = ::Opm::EclIO::OutputStream::ResultSet; + using Formatted = ::Opm::EclIO::OutputStream::Formatted; - const auto& udq = schedule.getUDQConfig(schedule.size() - 1); - const auto& init_config = st.getInitConfig(); - const char * restart_case = nullptr; - int restart_step = -1; + explicit SMSpecStreamDeferredCreation(const Opm::InitConfig& initcfg, + const Opm::EclipseGrid& grid, + const std::time_t start, + const Opm::UnitSystem::UnitType utype); - if (init_config.restartRequested( )) { - if (init_config.getRestartRootName().size() <= ECL_STRING8_LENGTH * SUMMARY_RESTART_SIZE) { - restart_case = init_config.getRestartRootName().c_str(); - restart_step = init_config.getRestartStep(); - } else - OpmLog::warning("Restart case too long - not embedded in SMSPEC file"); - } - ecl_sum.reset( ecl_sum_alloc_restart_writer2(basename, - restart_case, - restart_step, - st.getIOConfig().getFMTOUT(), - st.getIOConfig().getUNIFOUT(), - ":", - schedule.posixStartTime(), - true, - st.getInputGrid().getNX(), - st.getInputGrid().getNY(), - st.getInputGrid().getNZ())); - - /* register all keywords handlers and pair with the newly-registered ert - * entry. - */ - std::set< std::string > unsupported_keywords; - SummaryState summary_state(std::chrono::system_clock::from_time_t(schedule.getStartTime())); - for( const auto& node : sum ) { - ecl_smspec_type * smspec = ecl_sum_get_smspec(this->ecl_sum.get()); - std::string keyword = node.keyword(); - - const auto single_value_pair = single_values_units.find( keyword ); - const auto funs_pair = funs.find( keyword ); - const auto region_pair = region_units.find( keyword ); - const auto block_pair = block_units.find( keyword ); - - /* - All summary values of the type ECL_SMSPEC_MISC_VAR - and ECL_SMSPEC_FIELD_VAR must be passed explicitly - in the misc_values map when calling - add_timestep. - */ - if (single_value_pair != single_values_units.end()) { - auto node_type = node.category(); - if ((node_type != Cat::Field) && (node_type != Cat::Miscellaneous)) { - continue; - } - auto* nodeptr = ecl_smspec_add_node( smspec, keyword.c_str(), st.getUnits().name( single_value_pair->second ), 0); - this->handlers->single_value_nodes.emplace( keyword, nodeptr ); - } else if (region_pair != region_units.end()) { - auto* nodeptr = ecl_smspec_add_node( smspec, keyword.c_str(), node.number(), st.getUnits().name( region_pair->second ), 0); - this->handlers->region_nodes.emplace( std::make_pair(keyword, node.number()), nodeptr ); - } else if (block_pair != block_units.end()) { - if (node.category() != Cat::Block) - continue; - - int global_index = node.number() - 1; - if (!this->grid.cellActive(global_index)) - continue; - - auto* nodeptr = ecl_smspec_add_node( smspec, keyword.c_str(), node.number(), st.getUnits().name( block_pair->second ), 0 ); - this->handlers->block_nodes.emplace( std::make_pair(keyword, node.number()), nodeptr ); - } else if (funs_pair != funs.end()) { - auto node_type = node.category(); - - if ((node_type == Cat::Connection) || (node_type == Cat::Block)) { - int global_index = node.number() - 1; - if (!this->grid.cellActive(global_index)) - continue; - } - - /* get unit strings by calling each function with dummy input */ - const auto handle = funs_pair->second; - const std::vector< Well2 > dummy_wells; - - const fn_args no_args { dummy_wells, // Wells from Schedule object - 0, // Duration of time step - 0, // Simulation step - node.number(), - summary_state, // SummaryState - {}, // Well results - data::Wells - {}, // Region <-> cell mappings. - this->grid, - {}}; - - const auto val = handle( no_args ); - - auto * nodeptr = ecl_smspec_add_node( smspec, keyword.c_str(), node.namedEntity().c_str(), std::max(node.number(), 0), st.getUnits().name( val.unit ), 0 ); - this->handlers->handlers.emplace_back( nodeptr, handle ); - } else if (node.isUserDefined()) { - std::string udq_unit = "?????"; - const auto& udq_params = st.runspec().udqParams(); - - if (udq.has_unit(keyword)) - udq_unit = udq.unit(keyword); - - ecl_smspec_add_node(smspec, keyword.c_str(), node.namedEntity().c_str(), std::max(node.number(), 0), udq_unit.c_str(), udq_params.undefinedValue()); - } else - unsupported_keywords.insert(keyword); - } - for ( const auto& keyword : unsupported_keywords ) { - Opm::OpmLog::info("Summary keyword " + std::string(keyword) + " is unhandled"); - } - - // Guarantee existence of certain summary vectors (mostly rates and - // cumulative totals for wells, groups, and field) that are required - // for simulation restart. + std::unique_ptr + createStream(const ResultSet& rset, const Formatted& fmt) const { - auto& rvec = this->handlers->rstvec_backing_store; - auto& hndlrs = this->handlers->handlers; + return std::make_unique(rset, fmt, this->uconv(), + this->cartDims_, this->restart_, + this->start_); + } - // Required restart vectors for wells, groups, and field. - for (const auto& vector : requiredRestartVectors(schedule)) { - const auto& kw = vector.first; - const auto& entity = vector.second; +private: + Opm::UnitSystem::UnitType utype_; + std::array cartDims_; + Spec::StartTime start_; + Spec::RestartSpecification restart_{}; - const auto key = genKey(kw, entity); - if (ecl_sum_has_key(this->ecl_sum.get(), key.c_str())) { - // Vector already requested in SUMMARY section. - // Don't add a second evaluation of this. - continue; - } + Spec::UnitConvention uconv() const; +}; - auto func = funs.find(kw); - if (func == std::end(funs)) { - throw std::logic_error { - "Unable to find handler for '" + kw + "'" - }; - } - - rvec.push_back(makeRestartVectorSMSPEC(kw, entity)); - hndlrs.emplace_back(rvec.back().get(), func->second); - } - - // Required restart vectors for segments (if applicable). - for (const auto& segRes : requiredSegmentVectors(schedule)) { - const auto key = genKey(segRes); - if (ecl_sum_has_key(this->ecl_sum.get(), key.c_str())) { - // Segment result already requested in SUMMARY section. - // Don't add a second evaluation of this. - continue; - } - - auto func = funs.find(segRes.vector); - if (func == std::end(funs)) { - throw std::logic_error { - "Unable to find handler for '" + segRes.vector + "'" - }; - } - - rvec.push_back(makeRestartVectorSMSPEC(segRes)); - hndlrs.emplace_back(rvec.back().get(), func->second); - } +SMSpecStreamDeferredCreation:: +SMSpecStreamDeferredCreation(const Opm::InitConfig& initcfg, + const Opm::EclipseGrid& grid, + const std::time_t start, + const Opm::UnitSystem::UnitType utype) + : utype_ (utype) + , cartDims_(grid.getNXYZ()) + , start_ (std::chrono::system_clock::from_time_t(start)) +{ + if (initcfg.restartRequested()) { + this->restart_.root = initcfg.getRestartRootName(); + this->restart_.step = initcfg.getRestartStep(); } } -void Summary::eval( SummaryState& st, - int report_step, - double secs_elapsed, - const EclipseState& es, - const Schedule& schedule, - const data::Wells& wells , - const std::map& single_values, - const std::map>& region_values, - const std::map, double>& block_values) const { +SMSpecStreamDeferredCreation::Spec::UnitConvention +SMSpecStreamDeferredCreation::uconv() const +{ + using UType = ::Opm::UnitSystem::UnitType; - if (secs_elapsed < st.get_elapsed()) { - const auto& usys = es.getUnits(); - const auto elapsed = usys.from_si(measure::time, secs_elapsed); - const auto prev_el = usys.from_si(measure::time, st.get_elapsed()); - const auto unt = '[' + std::string{ usys.name(measure::time) } + ']'; + if (this->utype_ == UType::UNIT_TYPE_METRIC) + return Spec::UnitConvention::Metric; - throw std::invalid_argument { - "Elapsed time (" - + std::to_string(elapsed) + ' ' + unt - + ") must not precede previous elapsed time (" - + std::to_string(prev_el) + ' ' + unt - + "). Incorrect restart time?" - }; + if (this->utype_ == UType::UNIT_TYPE_FIELD) + return Spec::UnitConvention::Field; + + if (this->utype_ == UType::UNIT_TYPE_LAB) + return Spec::UnitConvention::Lab; + + if (this->utype_ == UType::UNIT_TYPE_PVT_M) + return Spec::UnitConvention::Pvt_M; + + throw std::invalid_argument { + "Unsupported Unit Convention (" + + std::to_string(static_cast(this->utype_)) + ')' + }; +} + +std::unique_ptr +makeDeferredSMSpecCreation(const Opm::EclipseState& es, + const Opm::EclipseGrid& grid, + const Opm::Schedule& sched) +{ + return std::make_unique + (es.cfg().init(), grid, sched.posixStartTime(), + es.getUnits().getType()); +} + +std::string makeUpperCase(std::string input) +{ + for (auto& c : input) { + const auto u = std::toupper(static_cast(c)); + c = static_cast(u); } + return input; +} + +Opm::EclIO::OutputStream::ResultSet +makeResultSet(const Opm::IOConfig& iocfg, const std::string& basenm) +{ + const auto& base = basenm.empty() + ? makeUpperCase(iocfg.getBaseName()) + : basenm; + + return { iocfg.getOutputDir(), base }; +} + +} // Anonymous namespace + +class Opm::out::Summary::SummaryImplementation +{ +public: + explicit SummaryImplementation(const EclipseState& es, + const SummaryConfig& sumcfg, + const EclipseGrid& grid, + const Schedule& sched, + const std::string& basename); + + SummaryImplementation(const SummaryImplementation& rhs) = delete; + SummaryImplementation(SummaryImplementation&& rhs) = default; + SummaryImplementation& operator=(const SummaryImplementation& rhs) = delete; + SummaryImplementation& operator=(SummaryImplementation&& rhs) = default; + + void eval(const EclipseState& es, + const Schedule& sched, + const int sim_step, + const double duration, + const data::WellRates& well_solution, + const GlobalProcessParameters& single_values, + const RegionParameters& region_values, + const BlockValues& block_values, + SummaryState& st) const; + + void internal_store(const SummaryState& st, const int report_step); + void write(); + +private: + struct MiniStep + { + int id{0}; + int seq{-1}; + std::vector params{}; + }; + + using EvalPtr = SummaryOutputParameters::EvalPtr; + + std::reference_wrapper grid_; + Opm::out::RegionCache regCache_; + + std::unique_ptr deferredSMSpec_; + + Opm::EclIO::OutputStream::ResultSet rset_; + Opm::EclIO::OutputStream::Formatted fmt_; + Opm::EclIO::OutputStream::Unified unif_; + + int miniStepID_{0}; + int prevCreate_{-1}; + int prevReportStepID_{-1}; + std::vector::size_type numUnwritten_{0}; + + SummaryOutputParameters outputParameters_{}; + std::vector requiredRestartParameters_{}; + std::vector valueKeys_{}; + std::vector unwritten_{}; + + std::unique_ptr smspec_{}; + std::unique_ptr stream_{}; + + void configureTimeVectors(const EclipseState& es); + + void configureSummaryInput(const EclipseState& es, + const SummaryConfig& sumcfg, + const EclipseGrid& grid, + const Schedule& sched); + + void configureRequiredRestartParameters(const SummaryConfig& sumcfg, + const Schedule& sched); + + MiniStep& getNextMiniStep(const int report_step); + const MiniStep& lastUnwritten() const; + + void write(const MiniStep& ms); + + void createSMSpecIfNecessary(); + void createSmryStreamIfNecessary(const int report_step); +}; + +Opm::out::Summary::SummaryImplementation:: +SummaryImplementation(const EclipseState& es, + const SummaryConfig& sumcfg, + const EclipseGrid& grid, + const Schedule& sched, + const std::string& basename) + : grid_ (std::cref(grid)) + , regCache_ (es.get3DProperties(), grid, sched) + , deferredSMSpec_(makeDeferredSMSpecCreation(es, grid, sched)) + , rset_ (makeResultSet(es.cfg().io(), basename)) + , fmt_ { es.cfg().io().getFMTOUT() } + , unif_ { es.cfg().io().getUNIFOUT() } +{ + this->configureTimeVectors(es); + this->configureSummaryInput(es, sumcfg, grid, sched); + this->configureRequiredRestartParameters(sumcfg, sched); +} + +void Opm::out::Summary::SummaryImplementation:: +internal_store(const SummaryState& st, const int report_step) +{ + auto& ms = this->getNextMiniStep(report_step); + + const auto nParam = this->valueKeys_.size(); + + for (auto i = decltype(nParam){0}; i < nParam; ++i) { + if (! st.has(this->valueKeys_[i])) + // Parameter not yet evaluated (e.g., well/group not + // yet active). Nothing to do here. + continue; + + ms.params[i] = st.get(this->valueKeys_[i]); + } +} + +void +Opm::out::Summary::SummaryImplementation:: +eval(const EclipseState& es, + const Schedule& sched, + const int sim_step, + const double duration, + const data::WellRates& well_solution, + const GlobalProcessParameters& single_values, + const RegionParameters& region_values, + const BlockValues& block_values, + Opm::SummaryState& st) const +{ + const Evaluator::InputData input { + es, sched, this->grid_, this->regCache_ + }; + + const Evaluator::SimulatorResults simRes { + well_solution, single_values, region_values, block_values + }; + + for (auto& evalPtr : this->outputParameters_.getEvaluators()) { + evalPtr->update(sim_step, duration, input, simRes, st); + } + + for (auto& evalPtr : this->requiredRestartParameters_) { + evalPtr->update(sim_step, duration, input, simRes, st); + } +} + +void Opm::out::Summary::SummaryImplementation::write() +{ + const auto zero = std::vector::size_type{0}; + if (this->numUnwritten_ == zero) + // No unwritten data. Nothing to do so return early. + return; + + this->createSMSpecIfNecessary(); + + if (this->prevReportStepID_ < this->lastUnwritten().seq) { + this->smspec_->write(this->outputParameters_.summarySpecification()); + } + + for (auto i = 0*this->numUnwritten_; i < this->numUnwritten_; ++i) + this->write(this->unwritten_[i]); + + // Eagerly output last set of parameters to permanent storage. + this->stream_->flushStream(); + + // Reset "unwritten" counter to reflect the fact that we've + // output all stored ministeps. + this->numUnwritten_ = zero; +} + +void Opm::out::Summary::SummaryImplementation::write(const MiniStep& ms) +{ + this->createSmryStreamIfNecessary(ms.seq); + + if (this->prevReportStepID_ < ms.seq) { + // XXX: Should probably write SEQHDR = 0 here since + /// we do not know the actual encoding needed. + this->stream_->write("SEQHDR", std::vector{ ms.seq }); + this->prevReportStepID_ = ms.seq; + } + + this->stream_->write("MINISTEP", std::vector{ ms.id }); + this->stream_->write("PARAMS" , ms.params); +} + +void +Opm::out::Summary::SummaryImplementation:: +configureTimeVectors(const EclipseState& es) +{ + const auto dfltwgname = std::string(":+:+:+:+"); + const auto dfltnum = 0; + + // XXX: Save keys might need/want to include a random component too. + auto makeKey = [this](const std::string& keyword) -> void + { + this->valueKeys_.push_back( + "SMSPEC.Internal." + keyword + ".Value.SAVE" + ); + }; + + // TIME + { + const auto& kw = std::string("TIME"); + makeKey(kw); + + const auto* utime = es.getUnits().name(UnitSystem::measure::time); + auto eval = std::make_unique(this->valueKeys_.back()); + + this->outputParameters_ + .makeParameter(kw, dfltwgname, dfltnum, utime, std::move(eval)); + } + +#if NOT_YET + // YEARS + { + const auto& kw = std::string("YEARS"); + makeKey(kw); + + auto eval = std::make_unique(this->valueKeys_.back()); + + this->outputParameters_ + .makeParameter(kw, dfltwgname, dfltnum, kw, std::move(eval)); + } +#endif // NOT_YET +} + +void +Opm::out::Summary::SummaryImplementation:: +configureSummaryInput(const EclipseState& es, + const SummaryConfig& sumcfg, + const EclipseGrid& grid, + const Schedule& sched) +{ + const auto st = SummaryState { + std::chrono::system_clock::from_time_t(sched.getStartTime()) + }; + + Evaluator::Factory fact { + es, grid, st, sched.getUDQConfig(sched.size() - 1) + }; + + auto unsuppkw = std::vector{}; + for (const auto& node : sumcfg) { + auto prmDescr = fact.create(node); + + if (! prmDescr.evaluator) { + // No known evaluation function/type for this keyword + unsuppkw.push_back(node.keyword()); + continue; + } + + // This keyword has a known evaluation method. + + this->valueKeys_.push_back(std::move(prmDescr.uniquekey)); + + this->outputParameters_ + .makeParameter(node.keyword(), + makeWGName(node.namedEntity()), + node.number(), + std::move(prmDescr.unit), + std::move(prmDescr.evaluator)); + } + + if (! unsuppkw.empty()) + reportUnsupportedKeywords(std::move(unsuppkw)); +} + +void +Opm::out::Summary::SummaryImplementation:: +configureRequiredRestartParameters(const SummaryConfig& sumcfg, + const Schedule& sched) +{ + auto makeEvaluator = [&sumcfg, this](const SummaryNode& node) -> void + { + if (sumcfg.hasSummaryKey(node.uniqueNodeKey())) + // Handler already exists. Don't add second evaluation. + return; + + auto fcnPos = funs.find(node.keyword()); + assert ((fcnPos != funs.end()) && + "Internal error creating required restart vectors"); + + auto eval = std::make_unique< + Evaluator::FunctionRelation>(node, fcnPos->second); + + this->requiredRestartParameters_.push_back(std::move(eval)); + }; + + for (const auto& node : requiredRestartVectors(sched)) + makeEvaluator(node); + + for (const auto& node : requiredSegmentVectors(sched)) + makeEvaluator(node); +} + +Opm::out::Summary::SummaryImplementation::MiniStep& +Opm::out::Summary::SummaryImplementation::getNextMiniStep(const int report_step) +{ + if (this->numUnwritten_ == this->unwritten_.size()) + this->unwritten_.emplace_back(); + + assert ((this->numUnwritten_ < this->unwritten_.size()) && + "Internal inconsistency in 'unwritten' counter"); + + auto& ms = this->unwritten_[this->numUnwritten_++]; + + ms.id = this->miniStepID_++; // MINSTEP IDs start at zero. + ms.seq = report_step; + + ms.params.resize(this->valueKeys_.size(), 0.0f); + + std::fill(ms.params.begin(), ms.params.end(), 0.0f); + + return ms; +} + +const Opm::out::Summary::SummaryImplementation::MiniStep& +Opm::out::Summary::SummaryImplementation::lastUnwritten() const +{ + assert (this->numUnwritten_ <= this->unwritten_.size()); + assert (this->numUnwritten_ > decltype(this->numUnwritten_){0}); + + return this->unwritten_[this->numUnwritten_ - 1]; +} + +void Opm::out::Summary::SummaryImplementation::createSMSpecIfNecessary() +{ + if (this->deferredSMSpec_) { + // We need an SMSPEC file and none exists. Create it and release + // the resources captured to make the deferred creation call. + this->smspec_ = this->deferredSMSpec_ + ->createStream(this->rset_, this->fmt_); + + this->deferredSMSpec_.reset(); + } +} + +void +Opm::out::Summary::SummaryImplementation:: +createSmryStreamIfNecessary(const int report_step) +{ + // Create stream if unset or if non-unified (separate) and new step. + + assert ((this->prevCreate_ <= report_step) && + "Inconsistent Report Step Sequence Detected"); + + const auto do_create = ! this->stream_ + || (! this->unif_.set && (this->prevCreate_ < report_step)); + + if (do_create) { + this->stream_ = Opm::EclIO::OutputStream:: + createSummaryFile(this->rset_, report_step, + this->fmt_, this->unif_); + + this->prevCreate_ = report_step; + } +} + +namespace { + +void validateElapsedTime(const double secs_elapsed, + const Opm::EclipseState& es, + const Opm::SummaryState& st) +{ + if (! (secs_elapsed < st.get_elapsed())) + return; + + const auto& usys = es.getUnits(); + const auto elapsed = usys.from_si(measure::time, secs_elapsed); + const auto prev_el = usys.from_si(measure::time, st.get_elapsed()); + const auto unt = '[' + std::string{ usys.name(measure::time) } + ']'; + + throw std::invalid_argument { + "Elapsed time (" + + std::to_string(elapsed) + ' ' + unt + + ") must not precede previous elapsed time (" + + std::to_string(prev_el) + ' ' + unt + + "). Incorrect restart time?" + }; +} + +} // Anonymous namespace + +namespace Opm { namespace out { + +Summary::Summary(const EclipseState& es, + const SummaryConfig& sumcfg, + const EclipseGrid& grid, + const Schedule& sched, + const std::string& basename) + : pImpl_(new SummaryImplementation(es, sumcfg, grid, sched, basename)) +{} + +void Summary::eval(SummaryState& st, + const int report_step, + const double secs_elapsed, + const EclipseState& es, + const Schedule& schedule, + const data::WellRates& well_solution, + const GlobalProcessParameters& single_values, + const RegionParameters& region_values, + const BlockValues& block_values) const +{ + validateElapsedTime(secs_elapsed, es, st); + const double duration = secs_elapsed - st.get_elapsed(); /* report_step is the number of the file we are about to write - i.e. for instance CASE.S$report_step @@ -1499,129 +2368,25 @@ void Summary::eval( SummaryState& st, * necessary to use when consulting the Schedule object. */ const auto sim_step = std::max( 0, report_step - 1 ); - for( auto& f : this->handlers->handlers ) { - const int num = smspec_node_get_num( f.first ); - double unit_applied_val = smspec_node_get_default( f.first ); + this->pImpl_->eval(es, schedule, sim_step, duration, + well_solution, single_values, + region_values, block_values, st); - if (need_wells(smspec_node_get_var_type(f.first ), - smspec_node_get_keyword(f.first))) { - - const auto schedule_wells = find_wells( schedule, f.first, sim_step, this->regionCache ); - /* - It is not a bug as such if the schedule_wells list comes back - empty; it just means that at the current timestep no relevant - wells have been defined and we do not calculate a value. - */ - if (schedule_wells.size() > 0) { - auto eff_factors = well_efficiency_factors( f.first, schedule, schedule_wells, sim_step ); - const auto val = f.second( { schedule_wells, - duration, - sim_step, - num, - st, - wells, - this->regionCache, - this->grid, - eff_factors}); - unit_applied_val = es.getUnits().from_si( val.unit, val.value ); - } - } else { - const auto val = f.second({ {}, - duration, - sim_step, - num, - st, - {}, - this->regionCache, - this->grid, - {} }); - unit_applied_val = es.getUnits().from_si( val.unit, val.value ); - } - - update(*f.first, unit_applied_val, st); - } - - for( const auto& value_pair : single_values ) { - const std::string key = value_pair.first; - const auto node_pair = this->handlers->single_value_nodes.find( key ); - if (node_pair != this->handlers->single_value_nodes.end()) { - const auto unit = single_values_units.at( key ); - double si_value = value_pair.second; - double output_value = es.getUnits().from_si(unit , si_value ); - update(*node_pair->second, output_value, st); - } - } - - for( const auto& value_pair : region_values ) { - const std::string key = value_pair.first; - for (size_t reg = 0; reg < value_pair.second.size(); ++reg) { - const auto node_pair = this->handlers->region_nodes.find( std::make_pair(key, reg+1) ); - if (node_pair != this->handlers->region_nodes.end()) { - const auto * nodeptr = node_pair->second; - const auto unit = region_units.at( key ); - - assert (smspec_node_get_num( nodeptr ) - 1 == static_cast(reg)); - double si_value = value_pair.second[reg]; - double output_value = es.getUnits().from_si(unit , si_value ); - update(*nodeptr, output_value, st); - } - } - } - - for( const auto& value_pair : block_values ) { - const std::pair key = value_pair.first; - const auto node_pair = this->handlers->block_nodes.find( key ); - if (node_pair != this->handlers->block_nodes.end()) { - const auto * nodeptr = node_pair->second; - const auto unit = block_units.at( key.first ); - double si_value = value_pair.second; - double output_value = es.getUnits().from_si(unit , si_value ); - update(*nodeptr, output_value, st); - } - } eval_udq(schedule, sim_step, st); + st.update_elapsed(duration); } - -void Summary::internal_store(const SummaryState& st, int report_step) { - auto* tstep = ecl_sum_add_tstep( this->ecl_sum.get(), report_step, st.get_elapsed() ); - const ecl_smspec_type * smspec = ecl_sum_get_smspec(this->ecl_sum.get()); - auto num_nodes = ecl_smspec_num_nodes(smspec); - for (int node_index = 0; node_index < num_nodes; node_index++) { - const auto& smspec_node = ecl_smspec_iget_node(smspec, node_index); - // The TIME node is treated specially, it is created internally in - // the ecl_sum instance when the timestep is created - and - // furthermore it is not in st SummaryState instance. - if (smspec_node.get_params_index() == ecl_smspec_get_time_index(smspec)) - continue; - - const std::string key = smspec_node.get_gen_key1(); - if (st.has(key)) - ecl_sum_tstep_iset(tstep, smspec_node.get_params_index(), st.get(key)); - - /* - else - OpmLog::warning("Have configured summary variable " + key + " for summary output - but it has not been calculated"); - */ - } +void Summary::add_timestep(const SummaryState& st, const int report_step) +{ + this->pImpl_->internal_store(st, report_step); } - -void Summary::add_timestep( const SummaryState& st, - int report_step) { - this->internal_store(st, report_step); +void Summary::write() const +{ + this->pImpl_->write(); } - -void Summary::write() const { - ecl_sum_fwrite( this->ecl_sum.get() ); -} - - Summary::~Summary() {} - - - }} // namespace Opm::out