/*
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
namespace {
struct SegmentResultDescriptor
{
std::string vector;
std::string well;
std::size_t segNumber;
};
std::vector requiredRestartVectors()
{
return {
"OPR", "WPR", "GPR", "VPR",
"OPT", "WPT", "GPT", "VPT",
"WIR", "GIR",
"WIT", "GIT",
"WCT", "GOR",
};
}
std::vector>
requiredRestartVectors(const ::Opm::Schedule& sched)
{
auto entities = std::vector>{};
const auto& vectors = requiredRestartVectors();
auto makeEntities = [&vectors, &entities]
(const char cat, const std::string& name)
{
for (const auto& vector : vectors) {
entities.emplace_back(cat + vector, name);
}
};
for (const auto* well : sched.getWells()) {
const auto& well_name = well->name();
makeEntities('W', well_name);
entities.emplace_back("WBHP" , well_name);
entities.emplace_back("WGVIR", well_name);
entities.emplace_back("WWVIR", well_name);
}
for (const auto* grp : sched.getGroups()) {
const auto& grp_name = grp->name();
if (grp_name != "FIELD") {
makeEntities('G', grp_name);
}
}
makeEntities('F', "FIELD");
return entities;
}
std::vector
requiredSegmentVectors(const ::Opm::Schedule& sched)
{
using SRD = SegmentResultDescriptor;
auto ret = std::vector{};
auto makeVectors =
[&ret](const std::string& well,
const std::size_t 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});
};
const auto last_timestep = sched.getTimeMap().last();
for (const auto* well : sched.getWells()) {
if (! well->isMultiSegment(last_timestep)) {
// Don't allocate MS summary vectors for non-MS wells.
continue;
}
const auto& wname = well->name();
const auto nSeg =
well->getWellSegments(last_timestep).size();
for (auto segID = 0*nSeg; segID < nSeg; ++segID) {
makeVectors(wname, segID + 1); // One-based
}
}
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, ":"));
}
} // namespace Anonymous
/*
* 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.
*/
namespace Opm {
namespace {
using rt = data::Rates::opt;
using measure = UnitSystem::measure;
constexpr const bool injector = true;
constexpr const bool producer = false;
constexpr const bool polymer = true;
/* 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;
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;
return lhs;
}
struct quantity {
double value;
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< const Well* >& schedule_wells;
double duration;
const int sim_step;
int num;
const data::Wells& wells;
const out::RegionCache& regionCache;
const 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< Phase > constexpr
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< 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; }
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;
}
template< rt phase, bool injection = true, bool polymer = false >
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 );
double concentration = polymer
? sched_well->getPolymerProperties( args.sim_step ).m_polymerConcentration
: 1;
const auto v = args.wells.at(name).rates.get(phase, 0.0) * eff_fac * concentration;
if( ( v > 0 ) == injection )
sum += v;
}
if( !injection ) sum *= -1;
if( polymer ) 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;
const auto ts = args.sim_step;
auto pred = [&wells,ts]( const Well* w ) {
const auto& name = w->name();
return w->isInjector( ts ) == 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, bool polymer = false >
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 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 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 data::Connection& c ) {
return c.index == global_index;
} );
if( completion == well_data.connections.end() ) return zero;
double eff_fac = efac( args.eff_factors, name );
double concentration = polymer
? well->getPolymerProperties( args.sim_step ).m_polymerConcentration
: 1;
auto v = completion->rates.get( phase, 0.0 ) * eff_fac * concentration;
if( ( v > 0 ) != injection ) return zero;
if( !injection ) v *= -1;
if( polymer ) return { v, measure::mass_rate };
return { v, rate_unit< phase >() };
}
template< rt phase, bool polymer = false >
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 );
double concentration = polymer
? well->getPolymerProperties( args.sim_step ).m_polymerConcentration
: 1;
auto v = segment->second.rates.get( phase, 0.0 ) * eff_fac * concentration;
//switch sign of rate - opposite convention in flow vs eclipse
v *= -1;
if( polymer ) return { v, measure::mass_rate };
return { v, rate_unit< phase >() };
}
inline quantity trans_factors ( const fn_args& args ) {
const quantity zero = { 0, measure::transmissibility };
if( args.schedule_wells.empty() ) 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& well = args.schedule_wells.front();
const auto& name = well->name();
if( args.wells.count( name ) == 0 ) return zero;
const auto& grid = args.grid;
const auto& connections = well->getConnections(args.sim_step);
const auto& connection = std::find_if(
connections.begin(),
connections.end(),
[=]( const Opm::Connection& c ) {
return grid.getGlobalIndex(c.getI(), c.getJ(), c.getK()) == global_index;
} );
if( connection == connections.end() ) return zero;
const auto& v = connection->CF() * connection->wellPi();
return { v, measure::transmissibility };
}
inline quantity spr ( 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;
const auto& v = segment->second.pressure;
return { v, 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 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 Well* sched_well = args.schedule_wells.front();
double bhp_hist;
if ( sched_well->isProducer( args.sim_step ) )
bhp_hist = sched_well->getProductionProperties( args.sim_step ).BHPH;
else
bhp_hist = sched_well->getInjectionProperties( args.sim_step ).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 Well* sched_well = args.schedule_wells.front();
double thp_hist;
if ( sched_well->isProducer( args.sim_step ) )
thp_hist = sched_well->getProductionProperties( args.sim_step ).THPH;
else
thp_hist = sched_well->getInjectionProperties( args.sim_step ).THPH;
return { thp_hist, measure::pressure };
}
template< 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 Well* sched_well : args.schedule_wells ){
double eff_fac = efac( args.eff_factors, sched_well->name() );
sum += sched_well->production_rate( phase, args.sim_step ) * eff_fac;
}
return { sum, rate_unit< phase >() };
}
template< Phase phase >
inline quantity injection_history( const fn_args& args ) {
double sum = 0.0;
for( const Well* sched_well : args.schedule_wells ){
double eff_fac = efac( args.eff_factors, sched_well->name() );
sum += sched_well->injection_rate( phase, args.sim_step ) * eff_fac;
}
return { sum, rate_unit< phase >() };
}
inline quantity res_vol_production_target( const fn_args& args ) {
double sum = 0.0;
for( const Well* sched_well : args.schedule_wells )
if (sched_well->getProductionProperties(args.sim_step).predictionMode)
sum += sched_well->getProductionProperties( args.sim_step ).ResVRate;
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 };
}
template