/*
Copyright 2019 Equinor ASA.
Copyright 2016 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see .
*/
#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 ParamCTorArgs
{
std::string kw;
Opm::EclIO::SummaryNode::Type type;
};
using p_cmode = Opm::Group::ProductionCMode;
const std::map pCModeToPCntlMode = {
{p_cmode::NONE, 0},
{p_cmode::ORAT, 1},
{p_cmode::WRAT, 2},
{p_cmode::GRAT, 3},
{p_cmode::LRAT, 4},
{p_cmode::CRAT, 9},
{p_cmode::RESV, 5},
{p_cmode::PRBL, 6},
{p_cmode::FLD, 0}, // same as NONE
};
using i_cmode = Opm::Group::InjectionCMode;
const std::map iCModeToICntlMode = {
{i_cmode::NONE, 0},
{i_cmode::RATE, 1},
{i_cmode::RESV, 2},
{i_cmode::REIN, 3},
{i_cmode::VREP, 4},
{i_cmode::FLD, 0}, // same as NONE
{i_cmode::SALE, 0}, // not used in E100
};
std::vector requiredRestartVectors()
{
using Type = ::Opm::EclIO::SummaryNode::Type;
return {
// 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{ "VIT" , Type::Total },
ParamCTorArgs{ "WITH", Type::Total },
ParamCTorArgs{ "GITH", Type::Total },
};
}
std::vector
requiredRestartVectors(const ::Opm::Schedule& sched)
{
auto entities = std::vector {};
const auto vectors = requiredRestartVectors();
const auto extra_well_vectors = std::vector {
{ "WTHP", Opm::EclIO::SummaryNode::Type::Pressure },
{ "WBHP", Opm::EclIO::SummaryNode::Type::Pressure },
{ "WGVIR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WWVIR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WOPGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WGPGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WWPGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WGIGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WWIGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "WMCTL", Opm::EclIO::SummaryNode::Type::Mode },
};
const auto extra_group_vectors = std::vector {
{ "GOPGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "GGPGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "GWPGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "GGIGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "GWIGR", Opm::EclIO::SummaryNode::Type::Rate },
{ "GMCTG", Opm::EclIO::SummaryNode::Type::Mode },
{ "GMCTP", Opm::EclIO::SummaryNode::Type::Mode },
{ "GMCTW", Opm::EclIO::SummaryNode::Type::Mode },
{ "GMWPR", Opm::EclIO::SummaryNode::Type::Mode },
{ "GMWIN", Opm::EclIO::SummaryNode::Type::Mode },
};
const auto extra_field_vectors = std::vector {
{ "FMCTG", Opm::EclIO::SummaryNode::Type::Mode },
{ "FMCTP", Opm::EclIO::SummaryNode::Type::Mode },
{ "FMCTW", Opm::EclIO::SummaryNode::Type::Mode },
{ "FMWPR", Opm::EclIO::SummaryNode::Type::Mode },
{ "FMWIN", Opm::EclIO::SummaryNode::Type::Mode },
};
using Cat = Opm::EclIO::SummaryNode::Category;
auto makeEntities = [&vectors, &entities]
(const char kwpref,
const Cat cat,
const std::vector& extra_vectors,
const std::string& name) -> void
{
const auto dflt_num = Opm::EclIO::SummaryNode::default_number;
// Recall: Cannot use emplace_back() for PODs.
for (const auto& vector : vectors) {
entities.push_back({ kwpref + vector.kw, cat,
vector.type, name, dflt_num, "" });
}
for (const auto& extra_vector : extra_vectors) {
entities.push_back({ extra_vector.kw, cat,
extra_vector.type, name, dflt_num, "" });
}
};
for (const auto& well_name : sched.wellNames()) {
makeEntities('W', Cat::Well, extra_well_vectors, well_name);
}
for (const auto& grp_name : sched.groupNames()) {
if (grp_name == "FIELD") { continue; }
makeEntities('G', Cat::Group, extra_group_vectors, grp_name);
}
makeEntities('F', Cat::Field, extra_field_vectors, "FIELD");
return entities;
}
std::vector
requiredSegmentVectors(const ::Opm::Schedule& sched)
{
std::vector ret {};
constexpr Opm::EclIO::SummaryNode::Category category { Opm::EclIO::SummaryNode::Category::Segment };
const std::vector> requiredVectors {
{ "SOFR", Opm::EclIO::SummaryNode::Type::Rate },
{ "SGFR", Opm::EclIO::SummaryNode::Type::Rate },
{ "SWFR", Opm::EclIO::SummaryNode::Type::Rate },
{ "SPR", Opm::EclIO::SummaryNode::Type::Pressure },
};
auto makeVectors =
[&](const std::string& well,
const int segNumber) -> void
{
for (const auto &requiredVector : requiredVectors) {
ret.push_back({requiredVector.first, category, requiredVector.second, well, segNumber, ""});
}
};
for (const auto& wname : sched.wellNames()) {
const auto& well = sched.getWellatEnd(wname);
if (! well.isMultiSegment()) {
// Don't allocate MS summary vectors for non-MS wells.
continue;
}
const auto nSeg = well.getSegments().size();
for (auto segID = 0*nSeg; segID < nSeg; ++segID) {
makeVectors(wname, segID + 1); // One-based
}
}
return ret;
}
Opm::TimeStampUTC make_sim_time(const Opm::Schedule& sched, const Opm::SummaryState& st, double sim_step) {
auto elapsed = st.get_elapsed() + sim_step;
return Opm::TimeStampUTC( sched.getStartTime() ) + std::chrono::duration(elapsed);
}
/*
* This class takes simulator state and parser-provided information and
* orchestrates ert to write simulation results as requested by the SUMMARY
* section in eclipse. The implementation is somewhat compact as a lot of the
* requested output may be similar-but-not-quite. Through various techniques
* the compiler writes a lot of this code for us.
*/
using rt = Opm::data::Rates::opt;
using measure = Opm::UnitSystem::measure;
constexpr const bool injector = true;
constexpr const bool producer = false;
/* Some numerical value with its unit tag embedded to enable caller to apply
* unit conversion. This removes a lot of boilerplate. ad-hoc solution to poor
* unit support in general.
*/
measure div_unit( measure denom, measure div ) {
if( denom == measure::gas_surface_rate &&
div == measure::liquid_surface_rate )
return measure::gas_oil_ratio;
if( denom == measure::liquid_surface_rate &&
div == measure::gas_surface_rate )
return measure::oil_gas_ratio;
if( denom == measure::liquid_surface_rate &&
div == measure::liquid_surface_rate )
return measure::water_cut;
if( denom == measure::liquid_surface_rate &&
div == measure::time )
return measure::liquid_surface_volume;
if( denom == measure::gas_surface_rate &&
div == measure::time )
return measure::gas_surface_volume;
if( denom == measure::mass_rate &&
div == measure::time )
return measure::mass;
if( denom == measure::mass_rate &&
div == measure::liquid_surface_rate )
return measure::polymer_density;
if( denom == measure::energy_rate &&
div == measure::time )
return measure::energy;
return measure::identity;
}
measure mul_unit( measure lhs, measure rhs ) {
if( lhs == rhs ) return lhs;
if( ( lhs == measure::liquid_surface_rate && rhs == measure::time ) ||
( rhs == measure::liquid_surface_rate && lhs == measure::time ) )
return measure::liquid_surface_volume;
if( ( lhs == measure::gas_surface_rate && rhs == measure::time ) ||
( rhs == measure::gas_surface_rate && lhs == measure::time ) )
return measure::gas_surface_volume;
if( ( lhs == measure::rate && rhs == measure::time ) ||
( rhs == measure::rate && lhs == measure::time ) )
return measure::volume;
if( lhs == measure::mass_rate && rhs == measure::time)
return measure::mass;
if( lhs == measure::energy_rate && rhs == measure::time)
return measure::energy;
return lhs;
}
struct quantity {
double value;
Opm::UnitSystem::measure unit;
quantity operator+( const quantity& rhs ) const {
assert( this->unit == rhs.unit );
return { this->value + rhs.value, this->unit };
}
quantity operator*( const quantity& rhs ) const {
return { this->value * rhs.value, mul_unit( this->unit, rhs.unit ) };
}
quantity operator/( const quantity& rhs ) const {
const auto res_unit = div_unit( this->unit, rhs.unit );
if( rhs.value == 0 ) return { 0.0, res_unit };
return { this->value / rhs.value, res_unit };
}
quantity operator/( double divisor ) const {
if( divisor == 0 ) return { 0.0, this->unit };
return { this->value / divisor , this->unit };
}
quantity& operator/=( double divisor ) {
if( divisor == 0 )
this->value = 0;
else
this->value /= divisor;
return *this;
}
quantity operator-( const quantity& rhs) const {
return { this->value - rhs.value, this->unit };
}
};
/*
* All functions must have the same parameters, so they're gathered in a struct
* and functions use whatever information they care about.
*
* schedule_wells are wells from the deck, provided by opm-parser. active_index
* is the index of the block in question. wells is simulation data.
*/
struct fn_args {
const std::vector& schedule_wells;
const std::string group_name;
double duration;
const int sim_step;
int num;
const std::string fip_region;
const Opm::SummaryState& st;
const Opm::data::Wells& wells;
const Opm::data::GroupAndNetworkValues& grp_nwrk;
const Opm::out::RegionCache& regionCache;
const Opm::EclipseGrid& grid;
const std::vector< std::pair< std::string, double > > eff_factors;
};
/* Since there are several enums in opm scattered about more-or-less
* representing the same thing. Since functions use template parameters to
* expand into the actual implementations we need a static way to determine
* what unit to tag the return value with.
*/
template< rt > constexpr
measure rate_unit() { return measure::liquid_surface_rate; }
template< Opm::Phase > constexpr
measure rate_unit() { return measure::liquid_surface_rate; }
template
measure rate_unit() { return measure::liquid_surface_rate; }
template<> constexpr
measure rate_unit< rt::gas >() { return measure::gas_surface_rate; }
template<> constexpr
measure rate_unit< Opm::Phase::GAS >() { return measure::gas_surface_rate; }
template<> constexpr
measure rate_unit< rt::solvent >() { return measure::gas_surface_rate; }
template<> constexpr
measure rate_unit< rt::reservoir_water >() { return measure::rate; }
template<> constexpr
measure rate_unit< rt::reservoir_oil >() { return measure::rate; }
template<> constexpr
measure rate_unit< rt::reservoir_gas >() { return measure::rate; }
template<> constexpr
measure rate_unit < rt::productivity_index_water > () { return measure::liquid_productivity_index; }
template<> constexpr
measure rate_unit < rt::productivity_index_oil > () { return measure::liquid_productivity_index; }
template<> constexpr
measure rate_unit < rt::productivity_index_gas > () { return measure::gas_productivity_index; }
template<> constexpr
measure rate_unit< rt::well_potential_water >() { return measure::liquid_surface_rate; }
template<> constexpr
measure rate_unit< rt::well_potential_oil >() { return measure::liquid_surface_rate; }
template<> constexpr
measure rate_unit< rt::well_potential_gas >() { return measure::gas_surface_rate; }
template <> constexpr
measure rate_unit() { return measure::gas_surface_rate; }
template <> constexpr
measure rate_unit() { return measure::rate; }
double efac( const std::vector>& eff_factors, const std::string& name ) {
auto it = std::find_if( eff_factors.begin(), eff_factors.end(),
[&] ( const std::pair< std::string, double > elem )
{ return elem.first == name; }
);
return (it != eff_factors.end()) ? it->second : 1;
}
/*
This is bit dangerous, exactly how the ALQ value should be interpreted varies
between the different VFP tables. The code here assumes - without checking -
that it represents gas lift rate.
*/
inline quantity glir( const fn_args& args ) {
double alq_rate = 0;
for (const auto& well : args.schedule_wells) {
auto xwPos = args.wells.find(well.name());
if (xwPos != args.wells.end())
alq_rate += xwPos->second.rates.get(rt::alq, 0.0);
}
return { alq_rate, measure::gas_surface_rate };
}
template< rt phase, bool injection = true >
inline quantity rate( const fn_args& args ) {
double sum = 0.0;
for( const auto& sched_well : args.schedule_wells ) {
const auto& name = sched_well.name();
if( args.wells.count( name ) == 0 ) continue;
double eff_fac = efac( args.eff_factors, name );
const auto v = args.wells.at(name).rates.get(phase, 0.0) * eff_fac;
if( ( v > 0 ) == injection )
sum += v;
}
if( !injection ) sum *= -1;
if (phase == rt::polymer || phase == rt::brine) return { sum, measure::mass_rate };
return { sum, rate_unit< phase >() };
}
template< bool injection >
inline quantity flowing( const fn_args& args ) {
const auto& wells = args.wells;
auto pred = [&wells]( const Opm::Well& w ) {
const auto& name = w.name();
return w.isInjector( ) == injection
&& wells.count( name ) > 0
&& wells.at( name ).flowing();
};
return { double( std::count_if( args.schedule_wells.begin(),
args.schedule_wells.end(),
pred ) ),
measure::identity };
}
template< rt phase, bool injection = true>
inline quantity crate( const fn_args& args ) {
const quantity zero = { 0, rate_unit< phase >() };
// The args.num value is the literal value which will go to the
// NUMS array in the eclipse SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
const size_t global_index = args.num - 1;
if( args.schedule_wells.empty() ) return zero;
const auto& well = args.schedule_wells.front();
const auto& name = well.name();
if( args.wells.count( name ) == 0 ) return zero;
const auto& well_data = args.wells.at( name );
const auto& completion = std::find_if( well_data.connections.begin(),
well_data.connections.end(),
[=]( const Opm::data::Connection& c ) {
return c.index == global_index;
} );
if (well_data.current_control.isProducer == injection) return zero;
if( completion == well_data.connections.end() ) return zero;
double eff_fac = efac( args.eff_factors, name );
auto v = completion->rates.get( phase, 0.0 ) * eff_fac;
if (!injection)
v *= -1;
if( phase == rt::polymer || phase == rt::brine ) return { v, measure::mass_rate };
return { v, rate_unit< phase >() };
}
template< rt phase>
inline quantity srate( const fn_args& args ) {
const quantity zero = { 0, rate_unit< phase >() };
// The args.num value is the literal value which will go to the
// NUMS array in the eclispe SMSPEC file; the values in this array
// are offset 1 - whereas we need to use this index here to look
// up a completion with offset 0.
const size_t segNumber = args.num;
if( args.schedule_wells.empty() ) return zero;
const auto& well = args.schedule_wells.front();
const auto& name = well.name();
if( args.wells.count( name ) == 0 ) return zero;
const auto& well_data = args.wells.at( name );
const auto& segment = well_data.segments.find(segNumber);
if( segment == well_data.segments.end() ) return zero;
double eff_fac = efac( args.eff_factors, name );
auto v = segment->second.rates.get( phase, 0.0 ) * eff_fac;
//switch sign of rate - opposite convention in flow vs eclipse
v *= -1;
if( phase == rt::polymer || phase == rt::brine ) return { v, measure::mass_rate };
return { v, rate_unit< phase >() };
}
inline quantity trans_factors ( const fn_args& args ) {
const quantity zero = { 0.0, measure::transmissibility };
if (args.schedule_wells.empty())
// No wells. Before simulation starts?
return zero;
auto xwPos = args.wells.find(args.schedule_wells.front().name());
if (xwPos == args.wells.end())
// No dynamic results for this well. Not open?
return zero;
// Like completion rate we need to look up a connection with offset 0.
const size_t global_index = args.num - 1;
const auto& connections = xwPos->second.connections;
auto connPos = std::find_if(connections.begin(), connections.end(),
[global_index](const Opm::data::Connection& c)
{
return c.index == global_index;
});
if (connPos == connections.end())
// No dynamic results for this connection.
return zero;
// Dynamic connection result's "trans_factor" includes PI-adjustment.
return { connPos->trans_factor, measure::transmissibility };
}
template
inline quantity segpress ( const fn_args& args ) {
const quantity zero = { 0, measure::pressure };
if( args.schedule_wells.empty() ) return zero;
// Like completion rate we need to look
// up a connection with offset 0.
const size_t segNumber = args.num;
if( args.schedule_wells.empty() ) return zero;
const auto& well = args.schedule_wells.front();
const auto& name = well.name();
if( args.wells.count( name ) == 0 ) return zero;
const auto& well_data = args.wells.at( name );
const auto& segment = well_data.segments.find(segNumber);
if( segment == well_data.segments.end() ) return zero;
return { segment->second.pressures[ix], measure::pressure };
}
inline quantity bhp( const fn_args& args ) {
const quantity zero = { 0, measure::pressure };
if( args.schedule_wells.empty() ) return zero;
const auto p = args.wells.find( args.schedule_wells.front().name() );
if( p == args.wells.end() ) return zero;
return { p->second.bhp, measure::pressure };
}
inline quantity temperature( const fn_args& args ) {
const quantity zero = { 0, measure::temperature };
if( args.schedule_wells.empty() ) return zero;
const auto p = args.wells.find( args.schedule_wells.front().name() );
if( p == args.wells.end() ) return zero;
return { p->second.temperature, measure::temperature };
}
inline quantity thp( const fn_args& args ) {
const quantity zero = { 0, measure::pressure };
if( args.schedule_wells.empty() ) return zero;
const auto p = args.wells.find( args.schedule_wells.front().name() );
if( p == args.wells.end() ) return zero;
return { p->second.thp, measure::pressure };
}
inline quantity bhp_history( const fn_args& args ) {
if( args.schedule_wells.empty() ) return { 0.0, measure::pressure };
const Opm::Well& sched_well = args.schedule_wells.front();
double bhp_hist;
if ( sched_well.isProducer( ) )
bhp_hist = sched_well.getProductionProperties().BHPH;
else
bhp_hist = sched_well.getInjectionProperties().BHPH;
return { bhp_hist, measure::pressure };
}
inline quantity thp_history( const fn_args& args ) {
if( args.schedule_wells.empty() ) return { 0.0, measure::pressure };
const Opm::Well& sched_well = args.schedule_wells.front();
double thp_hist;
if ( sched_well.isProducer() )
thp_hist = sched_well.getProductionProperties().THPH;
else
thp_hist = sched_well.getInjectionProperties().THPH;
return { thp_hist, measure::pressure };
}
inline quantity node_pressure(const fn_args& args)
{
auto nodePos = args.grp_nwrk.nodeData.find(args.group_name);
if (nodePos == args.grp_nwrk.nodeData.end()) {
return { 0.0, measure::pressure };
}
return { nodePos->second.pressure, measure::pressure };
}
template< Opm::Phase phase >
inline quantity production_history( const fn_args& args ) {
/*
* For well data, looking up historical rates (both for production and
* injection) before simulation actually starts is impossible and
* nonsensical. We therefore default to writing zero (which is what eclipse
* seems to do as well).
*/
double sum = 0.0;
for( const auto& sched_well : args.schedule_wells ){
double eff_fac = efac( args.eff_factors, sched_well.name() );
sum += sched_well.production_rate( args.st, phase ) * eff_fac;
}
return { sum, rate_unit< phase >() };
}
template< Opm::Phase phase >
inline quantity injection_history( const fn_args& args ) {
double sum = 0.0;
for( const auto& sched_well : args.schedule_wells ){
double eff_fac = efac( args.eff_factors, sched_well.name() );
sum += sched_well.injection_rate( args.st, phase ) * eff_fac;
}
return { sum, rate_unit< phase >() };
}
template< bool injection >
inline quantity abondoned_well( const fn_args& args ) {
std::size_t count = 0;
for (const auto& sched_well : args.schedule_wells) {
if (injection && !sched_well.hasInjected())
continue;
if (!injection && !sched_well.hasProduced())
continue;
const auto& well_name = sched_well.name();
auto well_iter = args.wells.find( well_name );
if (well_iter == args.wells.end()) {
count += 1;
continue;
}
count += !well_iter->second.flowing();
}
return { 1.0 * count, measure::identity };
}
inline quantity res_vol_production_target( const fn_args& args ) {
double sum = 0.0;
for( const Opm::Well& sched_well : args.schedule_wells )
if (sched_well.getProductionProperties().predictionMode)
sum += sched_well.getProductionProperties().ResVRate.getSI();
return { sum, measure::rate };
}
inline quantity duration( const fn_args& args ) {
return { args.duration, measure::time };
}
template