4424 lines
160 KiB
C++
4424 lines
160 KiB
C++
/*
|
|
Copyright 2021 Equinor ASA.
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <opm/output/eclipse/Summary.hpp>
|
|
#include <opm/output/eclipse/WStat.hpp>
|
|
|
|
#include <opm/common/OpmLog/OpmLog.hpp>
|
|
#include <opm/common/OpmLog/KeywordLocation.hpp>
|
|
#include <opm/common/utility/OpmInputError.hpp>
|
|
#include <opm/common/utility/TimeService.hpp>
|
|
|
|
#include <opm/output/eclipse/Inplace.hpp>
|
|
#include <opm/input/eclipse/EclipseState/Aquifer/AquiferConfig.hpp>
|
|
#include <opm/input/eclipse/EclipseState/Aquifer/AquiferCT.hpp>
|
|
#include <opm/input/eclipse/EclipseState/Aquifer/Aquifetp.hpp>
|
|
#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
|
|
#include <opm/input/eclipse/EclipseState/IOConfig/IOConfig.hpp>
|
|
#include <opm/input/eclipse/EclipseState/Phase.hpp>
|
|
#include <opm/input/eclipse/Schedule/Action/Actions.hpp>
|
|
#include <opm/input/eclipse/Schedule/GasLiftOpt.hpp>
|
|
#include <opm/input/eclipse/Schedule/Group/Group.hpp>
|
|
#include <opm/input/eclipse/Schedule/MSW/WellSegments.hpp>
|
|
#include <opm/input/eclipse/Schedule/ScheduleState.hpp>
|
|
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
|
#include <opm/input/eclipse/Schedule/SummaryState.hpp>
|
|
#include <opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp>
|
|
#include <opm/input/eclipse/Schedule/UDQ/UDQContext.hpp>
|
|
#include <opm/input/eclipse/Schedule/VFPProdTable.hpp>
|
|
#include <opm/input/eclipse/Schedule/Well/PAvgCalculatorCollection.hpp>
|
|
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
|
|
#include <opm/input/eclipse/Schedule/Well/WellConnections.hpp>
|
|
#include <opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
|
|
#include <opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp>
|
|
#include <opm/common/utility/TimeService.hpp>
|
|
|
|
#include <opm/input/eclipse/Units/UnitSystem.hpp>
|
|
#include <opm/input/eclipse/Units/Units.hpp>
|
|
|
|
#include <opm/io/eclipse/EclUtil.hpp>
|
|
#include <opm/io/eclipse/EclOutput.hpp>
|
|
#include <opm/io/eclipse/OutputStream.hpp>
|
|
#include <opm/io/eclipse/ExtSmryOutput.hpp>
|
|
|
|
#include <opm/output/data/Groups.hpp>
|
|
#include <opm/output/data/GuideRateValue.hpp>
|
|
#include <opm/output/data/Wells.hpp>
|
|
#include <opm/output/data/Aquifer.hpp>
|
|
#include <opm/output/eclipse/Inplace.hpp>
|
|
#include <opm/output/eclipse/RegionCache.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cctype>
|
|
#include <ctime>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <functional>
|
|
#include <initializer_list>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <numeric>
|
|
#include <regex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
template <> struct fmt::formatter<Opm::EclIO::SummaryNode::Category>: fmt::formatter<string_view> {
|
|
// parse is inherited from formatter<string_view>.
|
|
template <typename FormatContext>
|
|
auto format(Opm::EclIO::SummaryNode::Category c, FormatContext& ctx) {
|
|
using Category = Opm::EclIO::SummaryNode::Category;
|
|
string_view name = "unknown";
|
|
switch (c) {
|
|
case Category::Well: name = "Well"; break;
|
|
case Category::Group: name = "Group"; break;
|
|
case Category::Field: name = "Field"; break;
|
|
case Category::Region: name = "Region"; break;
|
|
case Category::Block: name = "Block"; break;
|
|
case Category::Connection: name = "Connection"; break;
|
|
case Category::Completion: name = "Completion"; break;
|
|
case Category::Segment: name = "Segment"; break;
|
|
case Category::Aquifer: name = "Aquifer"; break;
|
|
case Category::Node: name = "Node"; break;
|
|
case Category::Miscellaneous: name = "Miscellaneous"; break;
|
|
}
|
|
return formatter<string_view>::format(name, ctx);
|
|
}
|
|
};
|
|
|
|
namespace {
|
|
struct ParamCTorArgs
|
|
{
|
|
std::string kw;
|
|
Opm::EclIO::SummaryNode::Type type;
|
|
};
|
|
|
|
|
|
std::vector<ParamCTorArgs> 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{ "OPTS", Type::Total },
|
|
ParamCTorArgs{ "GPTS", 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{ "OIR" , Type::Rate },
|
|
ParamCTorArgs{ "WIR" , Type::Rate },
|
|
ParamCTorArgs{ "GIR" , Type::Rate },
|
|
ParamCTorArgs{ "VIR" , Type::Rate },
|
|
ParamCTorArgs{ "OPI" , Type::Rate },
|
|
ParamCTorArgs{ "WPI" , Type::Rate },
|
|
ParamCTorArgs{ "GPI" , Type::Rate },
|
|
ParamCTorArgs{ "OIT" , Type::Total },
|
|
ParamCTorArgs{ "WIT" , Type::Total },
|
|
ParamCTorArgs{ "GIT" , Type::Total },
|
|
ParamCTorArgs{ "VIT" , Type::Total },
|
|
ParamCTorArgs{ "WITH", Type::Total },
|
|
ParamCTorArgs{ "GITH", Type::Total },
|
|
};
|
|
}
|
|
|
|
std::vector<Opm::EclIO::SummaryNode>
|
|
requiredRestartVectors(const ::Opm::Schedule& sched)
|
|
{
|
|
auto entities = std::vector<Opm::EclIO::SummaryNode> {};
|
|
|
|
const auto vectors = requiredRestartVectors();
|
|
const auto extra_well_vectors = std::vector<ParamCTorArgs> {
|
|
{ "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 },
|
|
{ "WGLIR", Opm::EclIO::SummaryNode::Type::Rate },
|
|
};
|
|
const auto extra_group_vectors = std::vector<ParamCTorArgs> {
|
|
{ "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 },
|
|
{ "GPR", Opm::EclIO::SummaryNode::Type::Pressure },
|
|
};
|
|
|
|
const auto extra_field_vectors = std::vector<ParamCTorArgs> {
|
|
{ "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 },
|
|
};
|
|
|
|
const auto extra_connection_vectors = std::vector<ParamCTorArgs> {
|
|
{"COPR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"CWPR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"CGPR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"CVPR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"COPT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CWPT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CGPT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CVPT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"COIR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"CWIR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"CGIR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"CVIR", Opm::EclIO::SummaryNode::Type::Rate},
|
|
{"COIT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CWIT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CGIT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CVIT", Opm::EclIO::SummaryNode::Type::Total},
|
|
{"CPR", Opm::EclIO::SummaryNode::Type::Pressure},
|
|
{"CGOR", Opm::EclIO::SummaryNode::Type::Ratio},
|
|
};
|
|
|
|
using Cat = Opm::EclIO::SummaryNode::Category;
|
|
|
|
auto makeEntities = [&vectors, &entities]
|
|
(const char kwpref,
|
|
const Cat cat,
|
|
const std::vector<ParamCTorArgs>& 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);
|
|
|
|
const auto& well = sched.getWellatEnd(well_name);
|
|
for (const auto& conn : well.getConnections()) {
|
|
for (const auto& conn_vector : extra_connection_vectors)
|
|
entities.push_back( {conn_vector.kw, Cat::Connection, conn_vector.type, well.name(), static_cast<int>(conn.global_index() + 1), {}, {}} );
|
|
}
|
|
}
|
|
|
|
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<Opm::EclIO::SummaryNode>
|
|
requiredSegmentVectors(const ::Opm::Schedule& sched)
|
|
{
|
|
auto entities = std::vector<Opm::EclIO::SummaryNode> {};
|
|
|
|
const auto vectors = std::vector<ParamCTorArgs> {
|
|
{ "SOFR", Opm::EclIO::SummaryNode::Type::Rate },
|
|
{ "SGFR", Opm::EclIO::SummaryNode::Type::Rate },
|
|
{ "SWFR", Opm::EclIO::SummaryNode::Type::Rate },
|
|
{ "SPR", Opm::EclIO::SummaryNode::Type::Pressure },
|
|
};
|
|
|
|
using Cat = Opm::EclIO::SummaryNode::Category;
|
|
|
|
auto makeVectors = [&](const Opm::Well& well) -> void
|
|
{
|
|
const auto& wname = well.name();
|
|
const auto nSeg = static_cast<int>(well.getSegments().size());
|
|
|
|
for (auto segID = 0*nSeg + 1; segID <= nSeg; ++segID) {
|
|
for (const auto& vector : vectors) {
|
|
entities.push_back({ vector.kw, Cat::Segment,
|
|
vector.type, wname, segID, {}, {} });
|
|
}
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
makeVectors(well);
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
std::vector<Opm::EclIO::SummaryNode>
|
|
requiredAquiferVectors(const std::vector<int>& aquiferIDs)
|
|
{
|
|
auto entities = std::vector<Opm::EclIO::SummaryNode> {};
|
|
|
|
const auto vectors = std::vector<ParamCTorArgs> {
|
|
{ "AAQR" , Opm::EclIO::SummaryNode::Type::Rate },
|
|
{ "AAQP" , Opm::EclIO::SummaryNode::Type::Pressure },
|
|
{ "AAQT" , Opm::EclIO::SummaryNode::Type::Total },
|
|
{ "AAQTD", Opm::EclIO::SummaryNode::Type::Undefined },
|
|
{ "AAQPD", Opm::EclIO::SummaryNode::Type::Undefined },
|
|
};
|
|
|
|
using Cat = Opm::EclIO::SummaryNode::Category;
|
|
|
|
for (const auto& aquiferID : aquiferIDs) {
|
|
for (const auto& vector : vectors) {
|
|
entities.push_back({ vector.kw, Cat::Aquifer,
|
|
vector.type, "", aquiferID, {}, {} });
|
|
}
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
std::vector<Opm::EclIO::SummaryNode>
|
|
requiredNumericAquiferVectors(const std::vector<int>& aquiferIDs)
|
|
{
|
|
auto entities = std::vector<Opm::EclIO::SummaryNode> {};
|
|
|
|
const auto vectors = std::vector<ParamCTorArgs> {
|
|
{ "ANQR" , Opm::EclIO::SummaryNode::Type::Rate },
|
|
{ "ANQP" , Opm::EclIO::SummaryNode::Type::Pressure },
|
|
{ "ANQT" , Opm::EclIO::SummaryNode::Type::Total },
|
|
};
|
|
|
|
using Cat = Opm::EclIO::SummaryNode::Category;
|
|
|
|
for (const auto& aquiferID : aquiferIDs) {
|
|
for (const auto& vector : vectors) {
|
|
entities.push_back({ vector.kw, Cat::Aquifer,
|
|
vector.type, "", aquiferID, {}, {} });
|
|
}
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
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<double>(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<const Opm::Well*>& schedule_wells;
|
|
const std::string group_name;
|
|
const std::string keyword_name;
|
|
double duration;
|
|
const int sim_step;
|
|
int num;
|
|
const std::optional<std::variant<std::string, int>> extra_data;
|
|
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 Opm::Schedule& schedule;
|
|
const std::vector< std::pair< std::string, double > > eff_factors;
|
|
const Opm::Inplace& initial_inplace;
|
|
const Opm::Inplace& inplace;
|
|
const Opm::UnitSystem& unit_system;
|
|
};
|
|
|
|
/* 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 <Opm::data::GuideRateValue::Item>
|
|
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::dissolved_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<Opm::data::GuideRateValue::Item::Gas>() { return measure::gas_surface_rate; }
|
|
|
|
template <> constexpr
|
|
measure rate_unit<Opm::data::GuideRateValue::Item::ResV>() { return measure::rate; }
|
|
|
|
double efac( const std::vector<std::pair<std::string,double>>& eff_factors, const std::string& name)
|
|
{
|
|
auto it = std::find_if(eff_factors.begin(), eff_factors.end(),
|
|
[&name](const std::pair<std::string, double>& elem)
|
|
{
|
|
return elem.first == name;
|
|
});
|
|
|
|
return (it != eff_factors.end()) ? it->second : 1.0;
|
|
}
|
|
|
|
inline bool
|
|
has_vfp_table(const Opm::ScheduleState& sched_state,
|
|
int vfp_table_number)
|
|
{
|
|
return sched_state.vfpprod.has(vfp_table_number);
|
|
}
|
|
|
|
inline Opm::VFPProdTable::ALQ_TYPE
|
|
alq_type(const Opm::ScheduleState& sched_state,
|
|
int vfp_table_number)
|
|
{
|
|
return sched_state.vfpprod(vfp_table_number).getALQType();
|
|
}
|
|
|
|
inline quantity artificial_lift_quantity( const fn_args& args ) {
|
|
// Note: This function is intentionally supported only at the well level
|
|
// (meaning there's no loop over args.schedule_wells by intention). Its
|
|
// purpose is to calculate WALQ only.
|
|
|
|
// Note: in order to determine the correct dimension to use the Summary code
|
|
// calls the various evaluator functions with a default constructed fn_args
|
|
// instance. In the case of the WALQ function this does not really work,
|
|
// because the correct output dimension depends on exactly what physical
|
|
// quantity is represented by the ALQ - and that again requires quite some
|
|
// context to determine correctly. The current hack is that if WLIFTOPT is
|
|
// configured for at least one well we use dimension
|
|
// measure::gas_surface_rate - otherwise we use measure::identity.
|
|
|
|
auto dimension = measure::identity;
|
|
const auto& glo = args.schedule[args.sim_step].glo();
|
|
if (glo.num_wells() != 0)
|
|
dimension = measure::gas_surface_rate;
|
|
|
|
auto zero = quantity{0, dimension};
|
|
if (args.schedule_wells.empty()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
if (well->isInjector()) {
|
|
return zero;
|
|
}
|
|
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
const auto& production = well->productionControls(args.st);
|
|
if (!glo.has_well(well->name()))
|
|
return { production.alq_value, dimension};
|
|
|
|
const auto& sched_state = args.schedule[args.sim_step];
|
|
if (alq_type(sched_state, production.vfp_table_number) != Opm::VFPProdTable::ALQ_TYPE::ALQ_GRAT)
|
|
return zero;
|
|
|
|
const double eff_fac = efac(args.eff_factors, well->name());
|
|
auto alq_rate = eff_fac * xwPos->second.rates.get(rt::alq, production.alq_value);
|
|
return { alq_rate, dimension };
|
|
}
|
|
|
|
|
|
inline quantity glir( const fn_args& args ) {
|
|
if (args.schedule_wells.empty()) {
|
|
return { 0.0, measure::gas_surface_rate };
|
|
}
|
|
|
|
const auto& sched_state = args.schedule[args.sim_step];
|
|
|
|
double alq_rate = 0.0;
|
|
for (const auto* well : args.schedule_wells) {
|
|
if (well->isInjector()) {
|
|
continue;
|
|
}
|
|
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const auto& production = well->productionControls(args.st);
|
|
if (! has_vfp_table(sched_state, production.vfp_table_number)) {
|
|
continue;
|
|
}
|
|
|
|
const auto thisAlqType = alq_type(sched_state, production.vfp_table_number);
|
|
if (thisAlqType == Opm::VFPProdTable::ALQ_TYPE::ALQ_GRAT) {
|
|
const double eff_fac = efac(args.eff_factors, well->name());
|
|
alq_rate += eff_fac * xwPos->second.rates.get(rt::alq, production.alq_value);
|
|
}
|
|
|
|
if (thisAlqType == Opm::VFPProdTable::ALQ_TYPE::ALQ_IGLR) {
|
|
const double eff_fac = efac(args.eff_factors, well->name());
|
|
auto glr = production.alq_value;
|
|
auto wpr = xwPos->second.rates.get(rt::wat);
|
|
auto opr = xwPos->second.rates.get(rt::oil);
|
|
alq_rate -= eff_fac * glr * (wpr + opr);
|
|
}
|
|
}
|
|
|
|
return { alq_rate, measure::gas_surface_rate };
|
|
}
|
|
|
|
inline quantity wwirt( const fn_args& args ) {
|
|
const quantity zero = { 0, rate_unit< Opm::Phase::WATER >() };
|
|
|
|
if (args.schedule_wells.empty()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
const auto& wtype = well->wellType();
|
|
if (wtype.producer() || (wtype.injector_type() != Opm::InjectorType::WATER)) {
|
|
return zero;
|
|
}
|
|
|
|
const auto& injection = well->injectionControls(args.st);
|
|
return { injection.surface_rate, rate_unit<Opm::Phase::WATER>() };
|
|
}
|
|
|
|
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();
|
|
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const double eff_fac = efac(args.eff_factors, name);
|
|
const auto v = xwPos->second.rates.get(phase, 0.0) * eff_fac;
|
|
|
|
if ((v > 0.0) == injection) {
|
|
sum += v;
|
|
}
|
|
}
|
|
|
|
if (! injection) {
|
|
sum *= -1.0;
|
|
}
|
|
|
|
if (phase == rt::polymer || phase == rt::brine) {
|
|
return { sum, measure::mass_rate };
|
|
}
|
|
|
|
return { sum, rate_unit< phase >() };
|
|
}
|
|
|
|
template< rt tracer, rt phase, bool injection = true >
|
|
inline quantity ratetracer( const fn_args& args ) {
|
|
double sum = 0.0;
|
|
|
|
std::string tracer_name = args.keyword_name.substr(4);
|
|
|
|
for (const auto* sched_well : args.schedule_wells) {
|
|
const auto& name = sched_well->name();
|
|
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const double eff_fac = efac(args.eff_factors, name);
|
|
const auto v = xwPos->second.rates.get(tracer, 0.0, tracer_name) * eff_fac;
|
|
|
|
if ((v > 0.0) == injection) {
|
|
sum += v;
|
|
}
|
|
}
|
|
|
|
if (! injection) {
|
|
sum *= -1.0;
|
|
}
|
|
|
|
return { sum, rate_unit< phase >() };
|
|
}
|
|
|
|
template< rt phase, bool injection = true >
|
|
inline quantity ratel( const fn_args& args ) {
|
|
const auto unit = ((phase == rt::polymer) || (phase == rt::brine))
|
|
? measure::mass_rate : rate_unit<phase>();
|
|
|
|
const quantity zero = { 0.0, unit };
|
|
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
const auto& name = well->name();
|
|
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT) ||
|
|
(xwPos->second.current_control.isProducer == injection))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
const double eff_fac = efac(args.eff_factors, name);
|
|
const auto& well_data = xwPos->second;
|
|
|
|
double sum = 0;
|
|
const auto& connections = well->getConnections( args.num );
|
|
for (const auto* conn_ptr : connections) {
|
|
const size_t global_index = conn_ptr->global_index();
|
|
const auto& conn_data =
|
|
std::find_if(well_data.connections.begin(),
|
|
well_data.connections.end(),
|
|
[global_index](const Opm::data::Connection& cdata)
|
|
{
|
|
return cdata.index == global_index;
|
|
});
|
|
|
|
if (conn_data != well_data.connections.end()) {
|
|
sum += conn_data->rates.get(phase, 0.0) * eff_fac;
|
|
}
|
|
}
|
|
|
|
if (! injection) {
|
|
sum *= -1;
|
|
}
|
|
|
|
return { sum, unit };
|
|
}
|
|
|
|
inline quantity cpr( const fn_args& args ) {
|
|
const quantity zero = { 0, measure::pressure };
|
|
// 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 connection with offset 0.
|
|
const size_t global_index = args.num - 1;
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto& name = args.schedule_wells.front()->name();
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
return zero;
|
|
|
|
const auto& well_data = xwPos->second;
|
|
const auto& connection =
|
|
std::find_if(well_data.connections.begin(),
|
|
well_data.connections.end(),
|
|
[global_index](const Opm::data::Connection& c)
|
|
{
|
|
return c.index == global_index;
|
|
});
|
|
|
|
if (connection == well_data.connections.end())
|
|
return zero;
|
|
|
|
return { connection->pressure, measure::pressure };
|
|
}
|
|
|
|
template< rt phase, bool injection = true >
|
|
inline quantity cratel( const fn_args& args ) {
|
|
const auto unit = ((phase == rt::polymer) || (phase == rt::brine))
|
|
? measure::mass_rate : rate_unit<phase>();
|
|
|
|
const quantity zero = { 0.0, unit };
|
|
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
const auto& name = well->name();
|
|
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT) ||
|
|
(xwPos->second.current_control.isProducer == injection))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
const auto complnum = getCompletionNumberFromGlobalConnectionIndex(well->getConnections(), args.num - 1);
|
|
if (!complnum.has_value())
|
|
// Connection might not yet have come online.
|
|
return zero;
|
|
|
|
const auto& well_data = xwPos->second;
|
|
const double eff_fac = efac(args.eff_factors, name);
|
|
|
|
double sum = 0;
|
|
const auto& connections = well->getConnections(*complnum);
|
|
for (const auto& conn_ptr : connections) {
|
|
const size_t global_index = conn_ptr->global_index();
|
|
const auto& conn_data =
|
|
std::find_if(well_data.connections.begin(),
|
|
well_data.connections.end(),
|
|
[global_index] (const Opm::data::Connection& cdata)
|
|
{
|
|
return cdata.index == global_index;
|
|
});
|
|
|
|
if (conn_data != well_data.connections.end()) {
|
|
sum += conn_data->rates.get( phase, 0.0 ) * eff_fac;
|
|
}
|
|
}
|
|
|
|
if (! injection) {
|
|
sum *= -1;
|
|
}
|
|
|
|
return { sum, unit };
|
|
}
|
|
|
|
template< bool injection >
|
|
inline quantity flowing( const fn_args& args ) {
|
|
const auto& wells = args.wells;
|
|
auto pred = [&wells]( const Opm::Well* w ) -> bool
|
|
{
|
|
auto xwPos = wells.find(w->name());
|
|
return (xwPos != wells.end())
|
|
&& (w->isInjector( ) == injection)
|
|
&& (xwPos->second.dynamicStatus == Opm::Well::Status::OPEN)
|
|
&& xwPos->second.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 connection with offset 0.
|
|
const size_t global_index = args.num - 1;
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto& name = args.schedule_wells.front()->name();
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT) ||
|
|
(xwPos->second.current_control.isProducer == injection))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
const auto& well_data = xwPos->second;
|
|
const auto& completion =
|
|
std::find_if(well_data.connections.begin(),
|
|
well_data.connections.end(),
|
|
[global_index](const Opm::data::Connection& c)
|
|
{
|
|
return c.index == global_index;
|
|
});
|
|
|
|
if (completion == well_data.connections.end())
|
|
return zero;
|
|
|
|
const 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 <bool injection = true>
|
|
quantity crate_resv( const fn_args& args ) {
|
|
const quantity zero = { 0.0, rate_unit<rt::reservoir_oil>() };
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto& name = args.schedule_wells.front()->name();
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT) ||
|
|
(xwPos->second.current_control.isProducer == injection))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
// 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 connection with offset 0.
|
|
const auto global_index = static_cast<std::size_t>(args.num - 1);
|
|
|
|
const auto& well_data = xwPos->second;
|
|
const auto completion =
|
|
std::find_if(well_data.connections.begin(),
|
|
well_data.connections.end(),
|
|
[global_index](const Opm::data::Connection& c)
|
|
{
|
|
return c.index == global_index;
|
|
});
|
|
|
|
if (completion == well_data.connections.end())
|
|
return zero;
|
|
|
|
const auto eff_fac = efac( args.eff_factors, name );
|
|
auto v = completion->reservoir_rate * eff_fac;
|
|
if (! injection)
|
|
v *= -1;
|
|
|
|
return { v, rate_unit<rt::reservoir_oil>() };
|
|
}
|
|
|
|
template <rt phase>
|
|
inline quantity srate(const fn_args& args)
|
|
{
|
|
const quantity zero = { 0, rate_unit<phase>() };
|
|
if (args.schedule_wells.empty()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto& name = args.schedule_wells.front()->name();
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
const auto& well_data = xwPos->second;
|
|
|
|
const auto segNumber = static_cast<std::size_t>(args.num);
|
|
auto segPos = well_data.segments.find(segNumber);
|
|
if (segPos == well_data.segments.end()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto eff_fac = efac(args.eff_factors, name);
|
|
|
|
// Switch sign of rate--opposite convention in Flow vs ECLIPSE.
|
|
const auto v = -segPos->second.rates.get(phase, 0.0) * eff_fac;
|
|
|
|
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 connection 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 <Opm::data::SegmentPressures::Value ix>
|
|
inline quantity segpress ( const fn_args& args )
|
|
{
|
|
const quantity zero = { 0, measure::pressure };
|
|
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
// Like connection rate we need to look up a connection with offset 0.
|
|
const size_t segNumber = args.num;
|
|
|
|
const auto& well_data = xwPos->second;
|
|
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 wstat( const fn_args& args ) {
|
|
const quantity zero = { Opm::WStat::numeric::UNKNOWN, measure::identity};
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
const auto& sched_well = args.schedule_wells.front();
|
|
const auto& arg_well = args.wells.find(sched_well->name());
|
|
|
|
if (arg_well == args.wells.end() || arg_well->second.dynamicStatus == Opm::Well::Status::SHUT)
|
|
return {Opm::WStat::numeric::SHUT, measure::identity};
|
|
|
|
if (arg_well->second.dynamicStatus == Opm::Well::Status::STOP)
|
|
return {Opm::WStat::numeric::STOP, measure::identity};
|
|
|
|
if (sched_well->isInjector())
|
|
return {Opm::WStat::numeric::INJ, measure::identity};
|
|
|
|
return {Opm::WStat::numeric::PROD, measure::identity};
|
|
}
|
|
|
|
inline quantity bhp( const fn_args& args ) {
|
|
const quantity zero = { 0, measure::pressure };
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
const auto p = args.wells.find(args.schedule_wells.front()->name());
|
|
if ((p == args.wells.end()) ||
|
|
(p->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
return { p->second.bhp, measure::pressure };
|
|
}
|
|
|
|
/*
|
|
This function is slightly ugly - the evaluation of ROEW uses the already
|
|
calculated COPT results. We do not really have any formalism for such
|
|
dependencies between the summary vectors. For this particualar case there is a
|
|
hack in SummaryConfig which should ensure that this is safe.
|
|
*/
|
|
|
|
quantity roew(const fn_args& args) {
|
|
const quantity zero = { 0, measure::identity };
|
|
const auto& region_name = std::get<std::string>(*args.extra_data);
|
|
if (!args.initial_inplace.has( region_name, Opm::Inplace::Phase::OIL, args.num))
|
|
return zero;
|
|
|
|
double oil_prod = 0;
|
|
for (const auto& [well, global_index] : args.regionCache.connections(region_name, args.num)) {
|
|
const auto copt_key = fmt::format("COPT:{}:{}" , well, global_index + 1);
|
|
if (args.st.has(copt_key))
|
|
oil_prod += args.st.get(copt_key);
|
|
}
|
|
oil_prod = args.unit_system.to_si(Opm::UnitSystem::measure::volume, oil_prod);
|
|
return { oil_prod / args.initial_inplace.get( region_name, Opm::Inplace::Phase::OIL, args.num ) , measure::identity };
|
|
}
|
|
|
|
template< bool injection = true>
|
|
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()) ||
|
|
(p->second.dynamicStatus == Opm::Well::Status::SHUT) ||
|
|
(p->second.current_control.isProducer == injection))
|
|
{
|
|
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()) ||
|
|
(p->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
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 auto* sched_well = args.schedule_wells.front();
|
|
|
|
const auto bhp_hist = sched_well->isProducer()
|
|
? sched_well->getProductionProperties().BHPH
|
|
: 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 auto* sched_well = args.schedule_wells.front();
|
|
|
|
const auto thp_hist = sched_well->isProducer()
|
|
? sched_well->getProductionProperties().THPH
|
|
: 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) {
|
|
const 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) {
|
|
const 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 abandoned_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 auto* 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<rt phase , bool injection>
|
|
quantity region_rate( const fn_args& args ) {
|
|
double sum = 0;
|
|
const auto& well_connections = args.regionCache.connections( std::get<std::string>(*args.extra_data), args.num );
|
|
|
|
for (const auto& pair : well_connections) {
|
|
|
|
double eff_fac = efac( args.eff_factors, pair.first );
|
|
|
|
double rate = args.wells.get( pair.first , pair.second , phase ) * eff_fac;
|
|
|
|
// We are asking for the production rate in an injector - or
|
|
// opposite. We just clamp to zero.
|
|
if ((rate > 0) != injection)
|
|
rate = 0;
|
|
|
|
sum += rate;
|
|
}
|
|
|
|
if( injection )
|
|
return { sum, rate_unit< phase >() };
|
|
else
|
|
return { -sum, rate_unit< phase >() };
|
|
}
|
|
|
|
quantity rhpv(const fn_args& args) {
|
|
const auto& inplace = args.inplace;
|
|
const auto& region_name = std::get<std::string>(*args.extra_data);
|
|
if (inplace.has( region_name, Opm::Inplace::Phase::HydroCarbonPV, args.num ))
|
|
return { inplace.get( region_name, Opm::Inplace::Phase::HydroCarbonPV, args.num ), measure::volume };
|
|
else
|
|
return {0, measure::volume};
|
|
}
|
|
|
|
template < rt phase, bool outputProducer = true, bool outputInjector = true>
|
|
inline quantity potential_rate( const fn_args& args )
|
|
{
|
|
double sum = 0.0;
|
|
|
|
for (const auto* sched_well : args.schedule_wells) {
|
|
const auto& name = sched_well->name();
|
|
|
|
auto xwPos = args.wells.find(name);
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (sched_well->isInjector() && outputInjector) {
|
|
const auto v = xwPos->second.rates.get(phase, 0.0);
|
|
sum += v * efac(args.eff_factors, name);
|
|
}
|
|
else if (sched_well->isProducer() && outputProducer) {
|
|
const auto v = xwPos->second.rates.get(phase, 0.0);
|
|
sum += v * efac(args.eff_factors, name);
|
|
}
|
|
}
|
|
|
|
return { sum, rate_unit< phase >() };
|
|
}
|
|
|
|
inline quantity preferred_phase_productivty_index(const fn_args& args)
|
|
{
|
|
if (args.schedule_wells.empty())
|
|
return {0.0, rate_unit<rt::productivity_index_oil>()};
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
const auto preferred_phase = well->getPreferredPhase();
|
|
if (well->getStatus() == Opm::Well::Status::OPEN) {
|
|
switch (preferred_phase) {
|
|
case Opm::Phase::OIL:
|
|
return potential_rate<rt::productivity_index_oil>(args);
|
|
|
|
case Opm::Phase::GAS:
|
|
return potential_rate<rt::productivity_index_gas>(args);
|
|
|
|
case Opm::Phase::WATER:
|
|
return potential_rate<rt::productivity_index_water>(args);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch (preferred_phase) {
|
|
case Opm::Phase::OIL:
|
|
return {0.0, rate_unit<rt::productivity_index_oil>()};
|
|
|
|
case Opm::Phase::GAS:
|
|
return {0.0, rate_unit<rt::productivity_index_gas>()};
|
|
|
|
case Opm::Phase::WATER:
|
|
return {0.0, rate_unit<rt::productivity_index_water>()};
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
throw std::invalid_argument {
|
|
fmt::format("Unsupported \"preferred\" phase: {}",
|
|
static_cast<int>(args.schedule_wells.front()->getPreferredPhase()))
|
|
};
|
|
}
|
|
|
|
inline quantity connection_productivity_index(const fn_args& args)
|
|
{
|
|
const quantity zero = { 0.0, rate_unit<rt::productivity_index_oil>() };
|
|
|
|
if (args.schedule_wells.empty())
|
|
return zero;
|
|
|
|
auto xwPos = args.wells.find(args.schedule_wells.front()->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
return zero;
|
|
}
|
|
|
|
// 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 connection with offset 0.
|
|
const auto global_index = static_cast<std::size_t>(args.num) - 1;
|
|
|
|
const auto& xcon = xwPos->second.connections;
|
|
const auto& completion =
|
|
std::find_if(xcon.begin(), xcon.end(),
|
|
[global_index](const Opm::data::Connection& c)
|
|
{
|
|
return c.index == global_index;
|
|
});
|
|
|
|
if (completion == xcon.end())
|
|
return zero;
|
|
|
|
switch (args.schedule_wells.front()->getPreferredPhase()) {
|
|
case Opm::Phase::OIL:
|
|
return { completion->rates.get(rt::productivity_index_oil, 0.0),
|
|
rate_unit<rt::productivity_index_oil>() };
|
|
|
|
case Opm::Phase::GAS:
|
|
return { completion->rates.get(rt::productivity_index_gas, 0.0),
|
|
rate_unit<rt::productivity_index_gas>() };
|
|
|
|
case Opm::Phase::WATER:
|
|
return { completion->rates.get(rt::productivity_index_water, 0.0),
|
|
rate_unit<rt::productivity_index_water>() };
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
throw std::invalid_argument {
|
|
fmt::format("Unsupported \"preferred\" phase: {}",
|
|
static_cast<int>(args.schedule_wells.front()->getPreferredPhase()))
|
|
};
|
|
}
|
|
|
|
template < bool isGroup, bool Producer, bool waterInjector, bool gasInjector>
|
|
inline quantity group_control( const fn_args& args )
|
|
{
|
|
std::string g_name = "";
|
|
if (isGroup) {
|
|
const quantity zero = { static_cast<double>(0), Opm::UnitSystem::measure::identity};
|
|
if( args.group_name.empty() ) return zero;
|
|
|
|
g_name = args.group_name;
|
|
}
|
|
else {
|
|
g_name = "FIELD";
|
|
}
|
|
|
|
int cntl_mode = 0;
|
|
|
|
// production control
|
|
if (Producer) {
|
|
auto it_g = args.grp_nwrk.groupData.find(g_name);
|
|
if (it_g != args.grp_nwrk.groupData.end())
|
|
cntl_mode = Opm::Group::ProductionCMode2Int(it_g->second.currentControl.currentProdConstraint);
|
|
}
|
|
// water injection control
|
|
else if (waterInjector){
|
|
auto it_g = args.grp_nwrk.groupData.find(g_name);
|
|
if (it_g != args.grp_nwrk.groupData.end())
|
|
cntl_mode = Opm::Group::InjectionCMode2Int(it_g->second.currentControl.currentWaterInjectionConstraint);
|
|
}
|
|
|
|
// gas injection control
|
|
else if (gasInjector){
|
|
auto it_g = args.grp_nwrk.groupData.find(g_name);
|
|
if (it_g != args.grp_nwrk.groupData.end())
|
|
cntl_mode = Opm::Group::InjectionCMode2Int(it_g->second.currentControl.currentGasInjectionConstraint);
|
|
}
|
|
|
|
return {static_cast<double>(cntl_mode), Opm::UnitSystem::measure::identity};
|
|
}
|
|
|
|
namespace {
|
|
bool well_control_mode_defined(const ::Opm::data::Well& xw)
|
|
{
|
|
using PMode = ::Opm::Well::ProducerCMode;
|
|
using IMode = ::Opm::Well::InjectorCMode;
|
|
|
|
const auto& curr = xw.current_control;
|
|
|
|
return (curr.isProducer && (curr.prod != PMode::CMODE_UNDEFINED))
|
|
|| (!curr.isProducer && (curr.inj != IMode::CMODE_UNDEFINED));
|
|
}
|
|
}
|
|
|
|
inline quantity well_control_mode( const fn_args& args )
|
|
{
|
|
const auto unit = Opm::UnitSystem::measure::identity;
|
|
|
|
if (args.schedule_wells.empty()) {
|
|
// No wells. Possibly determining pertinent unit of measure
|
|
// during SMSPEC configuration.
|
|
return { 0.0, unit };
|
|
}
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT)) {
|
|
// No dynamic results for 'well'. Treat as shut/stopped.
|
|
return { 0.0, unit };
|
|
}
|
|
|
|
if (! well_control_mode_defined(xwPos->second)) {
|
|
// No dynamic control mode defined. Use input control.
|
|
const auto wmctl = Opm::Well::eclipseControlMode(*well, args.st);
|
|
|
|
return { static_cast<double>(wmctl), unit };
|
|
}
|
|
|
|
// Well has simulator-provided active control mode. Pick the
|
|
// appropriate value depending on well type (producer/injector).
|
|
const auto& curr = xwPos->second.current_control;
|
|
const auto wmctl = curr.isProducer
|
|
? Opm::Well::eclipseControlMode(curr.prod)
|
|
: Opm::Well::eclipseControlMode(curr.inj, well->injectorType());
|
|
|
|
return { static_cast<double>(wmctl), unit };
|
|
}
|
|
|
|
template <Opm::data::GuideRateValue::Item i>
|
|
quantity guiderate_value(const ::Opm::data::GuideRateValue& grvalue)
|
|
{
|
|
return { !grvalue.has(i) ? 0.0 : grvalue.get(i), rate_unit<i>() };
|
|
}
|
|
|
|
template <bool injection, Opm::data::GuideRateValue::Item i>
|
|
quantity group_guiderate(const fn_args& args)
|
|
{
|
|
auto xgPos = args.grp_nwrk.groupData.find(args.group_name);
|
|
if (xgPos == args.grp_nwrk.groupData.end()) {
|
|
return { 0.0, rate_unit<i>() };
|
|
}
|
|
|
|
return injection
|
|
? guiderate_value<i>(xgPos->second.guideRates.injection)
|
|
: guiderate_value<i>(xgPos->second.guideRates.production);
|
|
}
|
|
|
|
template <bool injection, Opm::data::GuideRateValue::Item i>
|
|
quantity well_guiderate(const fn_args& args)
|
|
{
|
|
if (args.schedule_wells.empty()) {
|
|
return { 0.0, rate_unit<i>() };
|
|
}
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
if (well->isInjector() != injection) {
|
|
return { 0.0, rate_unit<i>() };
|
|
}
|
|
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
return { 0.0, rate_unit<i>() };
|
|
}
|
|
|
|
return guiderate_value<i>(xwPos->second.guide_rates);
|
|
}
|
|
|
|
quantity well_efficiency_factor(const fn_args& args)
|
|
{
|
|
const auto zero = quantity { 0.0, measure::identity };
|
|
|
|
if (args.schedule_wells.empty()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
// Non-flowing wells have a zero efficiency factor
|
|
return zero;
|
|
}
|
|
|
|
return { well->getEfficiencyFactor(), measure::identity };
|
|
}
|
|
|
|
quantity well_efficiency_factor_grouptree(const fn_args& args)
|
|
{
|
|
const auto zero = quantity { 0.0, measure::identity };
|
|
|
|
if (args.schedule_wells.empty()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto* well = args.schedule_wells.front();
|
|
|
|
auto xwPos = args.wells.find(well->name());
|
|
if ((xwPos == args.wells.end()) ||
|
|
(xwPos->second.dynamicStatus == Opm::Well::Status::SHUT))
|
|
{
|
|
// Non-flowing wells have a zero efficiency factor
|
|
return zero;
|
|
}
|
|
|
|
auto factor = well->getEfficiencyFactor();
|
|
auto parent = well->groupName();
|
|
while (parent != "FIELD") {
|
|
const auto& grp = args.schedule[args.sim_step].groups(parent);
|
|
factor *= grp.getGroupEfficiencyFactor();
|
|
|
|
const auto prnt = grp.control_group();
|
|
parent = prnt.has_value() ? prnt.value() : "FIELD";
|
|
}
|
|
|
|
return { factor, measure::identity };
|
|
}
|
|
|
|
quantity group_efficiency_factor(const fn_args& args)
|
|
{
|
|
const auto zero = quantity { 0.0, measure::identity };
|
|
|
|
if (args.schedule_wells.empty()) {
|
|
return zero;
|
|
}
|
|
|
|
const auto gefac =
|
|
args.schedule[args.sim_step].groups(args.group_name)
|
|
.getGroupEfficiencyFactor();
|
|
|
|
return { gefac, measure::identity };
|
|
}
|
|
|
|
/*
|
|
* 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 }; }
|
|
|
|
template< typename F, typename G >
|
|
auto sum( F f, G g ) -> bin_op< F, G, std::plus< quantity > >
|
|
{ return { f, g }; }
|
|
|
|
template< typename F, typename G >
|
|
auto div( F f, G g ) -> bin_op< F, G, std::divides< quantity > >
|
|
{ return { f, g }; }
|
|
|
|
template< typename F, typename G >
|
|
auto sub( F f, G g ) -> bin_op< F, G, std::minus< quantity > >
|
|
{ return { f, g }; }
|
|
|
|
using ofun = std::function< quantity( const fn_args& ) >;
|
|
|
|
static const std::unordered_map< std::string, ofun > funs = {
|
|
{ "WWIR", rate< rt::wat, injector > },
|
|
{ "WWIRT", wwirt },
|
|
{ "WOIR", rate< rt::oil, injector > },
|
|
{ "WGIR", rate< rt::gas, injector > },
|
|
{ "WEIR", rate< rt::energy, injector > },
|
|
{ "WTIRHEA", rate< rt::energy, injector > },
|
|
{ "WNIR", rate< rt::solvent, injector > },
|
|
{ "WCIR", rate< rt::polymer, injector > },
|
|
{ "WSIR", rate< rt::brine, injector > },
|
|
// Allow phase specific interpretation of tracer related summary keywords
|
|
{ "WTIR#W", ratetracer< rt::tracer, rt::wat, injector > }, // #W: Water tracers
|
|
{ "WTIR#O", ratetracer< rt::tracer, rt::oil, injector > }, // #O: Oil tracers
|
|
{ "WTIR#G", ratetracer< rt::tracer, rt::gas, injector > }, // #G: Gas tracers
|
|
{ "WTIC#W", div( ratetracer< rt::tracer, rt::wat, injector >, rate< rt::wat, injector >) },
|
|
{ "WTIC#O", div( ratetracer< rt::tracer, rt::oil, injector >, rate< rt::oil, injector >) },
|
|
{ "WTIC#G", div( ratetracer< rt::tracer, rt::gas, injector >, rate< rt::gas, injector >) },
|
|
{ "WVIR", sum( sum( rate< rt::reservoir_water, injector >, rate< rt::reservoir_oil, injector > ),
|
|
rate< rt::reservoir_gas, injector > ) },
|
|
{ "WGIGR", well_guiderate<injector, Opm::data::GuideRateValue::Item::Gas> },
|
|
{ "WWIGR", well_guiderate<injector, Opm::data::GuideRateValue::Item::Water> },
|
|
|
|
{ "WWIT", mul( rate< rt::wat, injector >, duration ) },
|
|
{ "WOIT", mul( rate< rt::oil, injector >, duration ) },
|
|
{ "WGIT", mul( rate< rt::gas, injector >, duration ) },
|
|
{ "WEIT", mul( rate< rt::energy, injector >, duration ) },
|
|
{ "WTITHEA", mul( rate< rt::energy, injector >, duration ) },
|
|
{ "WNIT", mul( rate< rt::solvent, injector >, duration ) },
|
|
{ "WCIT", mul( rate< rt::polymer, injector >, duration ) },
|
|
{ "WSIT", mul( rate< rt::brine, injector >, duration ) },
|
|
{ "WTIT#W", mul( ratetracer< rt::tracer, rt::wat, injector >, duration ) },
|
|
{ "WTIT#O", mul( ratetracer< rt::tracer, rt::oil, injector >, duration ) },
|
|
{ "WTIT#G", mul( ratetracer< rt::tracer, rt::gas, injector >, duration ) },
|
|
{ "WVIT", mul( sum( sum( rate< rt::reservoir_water, injector >, rate< rt::reservoir_oil, injector > ),
|
|
rate< rt::reservoir_gas, injector > ), duration ) },
|
|
|
|
{ "WWPR", rate< rt::wat, producer > },
|
|
{ "WOPR", rate< rt::oil, producer > },
|
|
{ "WWPTL",mul(ratel< rt::wat, producer >, duration) },
|
|
{ "WGPTL",mul(ratel< rt::gas, producer >, duration) },
|
|
{ "WOPTL",mul(ratel< rt::oil, producer >, duration) },
|
|
{ "WWPRL",ratel< rt::wat, producer > },
|
|
{ "WGPRL",ratel< rt::gas, producer > },
|
|
{ "WOPRL",ratel< rt::oil, producer > },
|
|
{ "WOFRL",ratel< rt::oil, producer > },
|
|
{ "WWIRL",ratel< rt::wat, injector> },
|
|
{ "WWITL",mul(ratel< rt::wat, injector>, duration) },
|
|
{ "WGIRL",ratel< rt::gas, injector> },
|
|
{ "WGITL",mul(ratel< rt::gas, injector>, duration) },
|
|
{ "WLPTL",mul( sum(ratel<rt::wat, producer>, ratel<rt::oil, producer>), duration)},
|
|
{ "WWCTL", div( ratel< rt::wat, producer >,
|
|
sum( ratel< rt::wat, producer >, ratel< rt::oil, producer > ) ) },
|
|
{ "WGORL", div( ratel< rt::gas, producer >, ratel< rt::oil, producer > ) },
|
|
{ "WGPR", rate< rt::gas, producer > },
|
|
{ "WEPR", rate< rt::energy, producer > },
|
|
{ "WTPRHEA", rate< rt::energy, producer > },
|
|
{ "WGLIR", glir},
|
|
{ "WALQ", artificial_lift_quantity },
|
|
{ "WNPR", rate< rt::solvent, producer > },
|
|
{ "WCPR", rate< rt::polymer, producer > },
|
|
{ "WSPR", rate< rt::brine, producer > },
|
|
{ "WTPR#W", ratetracer< rt::tracer, rt::wat, producer > },
|
|
{ "WTPR#O", ratetracer< rt::tracer, rt::oil, producer > },
|
|
{ "WTPR#G", ratetracer< rt::tracer, rt::gas, producer > },
|
|
{ "WTPC#W", div( ratetracer< rt::tracer, rt::wat, producer >, rate< rt::wat, producer >) },
|
|
{ "WTPC#O", div( ratetracer< rt::tracer, rt::oil, producer >, rate< rt::oil, producer >) },
|
|
{ "WTPC#G", div( ratetracer< rt::tracer, rt::gas, producer >, rate< rt::gas, producer >) },
|
|
{ "WCPC", div( rate< rt::polymer, producer >, rate< rt::wat, producer >) },
|
|
{ "WSPC", div( rate< rt::brine, producer >, rate< rt::wat, producer >) },
|
|
|
|
{ "WOPGR", well_guiderate<producer, Opm::data::GuideRateValue::Item::Oil> },
|
|
{ "WGPGR", well_guiderate<producer, Opm::data::GuideRateValue::Item::Gas> },
|
|
{ "WWPGR", well_guiderate<producer, Opm::data::GuideRateValue::Item::Water> },
|
|
{ "WVPGR", well_guiderate<producer, Opm::data::GuideRateValue::Item::ResV> },
|
|
|
|
{ "WGPRS", rate< rt::dissolved_gas, producer > },
|
|
{ "WGPRF", sub( rate< rt::gas, producer >, rate< rt::dissolved_gas, producer > ) },
|
|
{ "WOPRS", rate< rt::vaporized_oil, producer > },
|
|
{ "WOPRF", sub (rate < rt::oil, producer >, rate< rt::vaporized_oil, producer > ) },
|
|
{ "WVPR", sum( sum( rate< rt::reservoir_water, producer >, rate< rt::reservoir_oil, producer > ),
|
|
rate< rt::reservoir_gas, producer > ) },
|
|
{ "WGVPR", rate< rt::reservoir_gas, producer > },
|
|
|
|
{ "WLPR", sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) },
|
|
{ "WWPT", mul( rate< rt::wat, producer >, duration ) },
|
|
{ "WOPT", mul( rate< rt::oil, producer >, duration ) },
|
|
{ "WGPT", mul( rate< rt::gas, producer >, duration ) },
|
|
{ "WEPT", mul( rate< rt::energy, producer >, duration ) },
|
|
{ "WTPTHEA", mul( rate< rt::energy, producer >, duration ) },
|
|
{ "WNPT", mul( rate< rt::solvent, producer >, duration ) },
|
|
{ "WCPT", mul( rate< rt::polymer, producer >, duration ) },
|
|
{ "WSPT", mul( rate< rt::brine, producer >, duration ) },
|
|
{ "WTPT#W", mul( ratetracer< rt::tracer, rt::wat, producer >, duration ) },
|
|
{ "WTPT#O", mul( ratetracer< rt::tracer, rt::oil, producer >, duration ) },
|
|
{ "WTPT#G", mul( ratetracer< rt::tracer, rt::gas, producer >, duration ) },
|
|
{ "WLPT", mul( sum( rate< rt::wat, producer >, rate< rt::oil, producer > ),
|
|
duration ) },
|
|
{ "WGPTS", mul( rate< rt::dissolved_gas, producer >, duration )},
|
|
{ "WGPTF", sub( mul( rate< rt::gas, producer >, duration ),
|
|
mul( rate< rt::dissolved_gas, producer >, duration ))},
|
|
{ "WOPTS", mul( rate< rt::vaporized_oil, producer >, duration )},
|
|
{ "WOPTF", sub( mul( rate< rt::oil, producer >, duration ),
|
|
mul( rate< rt::vaporized_oil, producer >, duration ))},
|
|
{ "WVPT", mul( sum( sum( rate< rt::reservoir_water, producer >, rate< rt::reservoir_oil, producer > ),
|
|
rate< rt::reservoir_gas, producer > ), duration ) },
|
|
|
|
{ "WWCT", div( rate< rt::wat, producer >,
|
|
sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) },
|
|
{ "GWCT", div( rate< rt::wat, producer >,
|
|
sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) },
|
|
{ "WGOR", div( rate< rt::gas, producer >, rate< rt::oil, producer > ) },
|
|
{ "GGOR", div( rate< rt::gas, producer >, rate< rt::oil, producer > ) },
|
|
{ "WGLR", div( rate< rt::gas, producer >,
|
|
sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) },
|
|
|
|
{ "WSTAT", wstat },
|
|
{ "WBHP", bhp },
|
|
{ "WTHP", thp },
|
|
{ "WTPCHEA", temperature< producer >},
|
|
{ "WTICHEA", temperature< injector >},
|
|
{ "WVPRT", res_vol_production_target },
|
|
|
|
{ "WMCTL", well_control_mode },
|
|
|
|
{ "GWIR", rate< rt::wat, injector > },
|
|
{ "WGVIR", rate< rt::reservoir_gas, injector >},
|
|
{ "WWVIR", rate< rt::reservoir_water, injector >},
|
|
{ "GOIR", rate< rt::oil, injector > },
|
|
{ "GGIR", rate< rt::gas, injector > },
|
|
{ "GEIR", rate< rt::energy, injector > },
|
|
{ "GTIRHEA", rate< rt::energy, injector > },
|
|
{ "GNIR", rate< rt::solvent, injector > },
|
|
{ "GCIR", rate< rt::polymer, injector > },
|
|
{ "GSIR", rate< rt::brine, injector > },
|
|
{ "GVIR", sum( sum( rate< rt::reservoir_water, injector >, rate< rt::reservoir_oil, injector > ),
|
|
rate< rt::reservoir_gas, injector > ) },
|
|
|
|
{ "GGIGR", group_guiderate<injector, Opm::data::GuideRateValue::Item::Gas> },
|
|
{ "GWIGR", group_guiderate<injector, Opm::data::GuideRateValue::Item::Water> },
|
|
|
|
{ "GWIT", mul( rate< rt::wat, injector >, duration ) },
|
|
{ "GOIT", mul( rate< rt::oil, injector >, duration ) },
|
|
{ "GGIT", mul( rate< rt::gas, injector >, duration ) },
|
|
{ "GEIT", mul( rate< rt::energy, injector >, duration ) },
|
|
{ "GTITHEA", mul( rate< rt::energy, injector >, duration ) },
|
|
{ "GNIT", mul( rate< rt::solvent, injector >, duration ) },
|
|
{ "GCIT", mul( rate< rt::polymer, injector >, duration ) },
|
|
{ "GSIT", mul( rate< rt::brine, injector >, duration ) },
|
|
{ "GVIT", mul( sum( sum( rate< rt::reservoir_water, injector >, rate< rt::reservoir_oil, injector > ),
|
|
rate< rt::reservoir_gas, injector > ), duration ) },
|
|
|
|
{ "GWPR", rate< rt::wat, producer > },
|
|
{ "GOPR", rate< rt::oil, producer > },
|
|
{ "GGPR", rate< rt::gas, producer > },
|
|
{ "GEPR", rate< rt::energy, producer > },
|
|
{ "GTPRHEA", rate< rt::energy, producer > },
|
|
{ "GGLIR", glir },
|
|
{ "GNPR", rate< rt::solvent, producer > },
|
|
{ "GCPR", rate< rt::polymer, producer > },
|
|
{ "GSPR", rate< rt::brine, producer > },
|
|
{ "GCPC", div( rate< rt::polymer, producer >, rate< rt::wat, producer >) },
|
|
{ "GSPC", div( rate< rt::brine, producer >, rate< rt::wat, producer >) },
|
|
{ "GOPRS", rate< rt::vaporized_oil, producer > },
|
|
{ "GOPRF", sub (rate < rt::oil, producer >, rate< rt::vaporized_oil, producer > ) },
|
|
{ "GLPR", sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) },
|
|
{ "GVPR", sum( sum( rate< rt::reservoir_water, producer >, rate< rt::reservoir_oil, producer > ),
|
|
rate< rt::reservoir_gas, producer > ) },
|
|
|
|
{ "GOPGR", group_guiderate<producer, Opm::data::GuideRateValue::Item::Oil> },
|
|
{ "GGPGR", group_guiderate<producer, Opm::data::GuideRateValue::Item::Gas> },
|
|
{ "GWPGR", group_guiderate<producer, Opm::data::GuideRateValue::Item::Water> },
|
|
{ "GVPGR", group_guiderate<producer, Opm::data::GuideRateValue::Item::ResV> },
|
|
|
|
{ "GPR", node_pressure },
|
|
|
|
{ "GWPT", mul( rate< rt::wat, producer >, duration ) },
|
|
{ "GOPT", mul( rate< rt::oil, producer >, duration ) },
|
|
{ "GGPT", mul( rate< rt::gas, producer >, duration ) },
|
|
{ "GEPT", mul( rate< rt::energy, producer >, duration ) },
|
|
{ "GTPTHEA", mul( rate< rt::energy, producer >, duration ) },
|
|
{ "GNPT", mul( rate< rt::solvent, producer >, duration ) },
|
|
{ "GCPT", mul( rate< rt::polymer, producer >, duration ) },
|
|
{ "GOPTS", mul( rate< rt::vaporized_oil, producer >, duration ) },
|
|
{ "GOPTF", mul( sub (rate < rt::oil, producer >,
|
|
rate< rt::vaporized_oil, producer > ),
|
|
duration ) },
|
|
{ "GLPT", mul( sum( rate< rt::wat, producer >, rate< rt::oil, producer > ),
|
|
duration ) },
|
|
{ "GVPT", mul( sum( sum( rate< rt::reservoir_water, producer >, rate< rt::reservoir_oil, producer > ),
|
|
rate< rt::reservoir_gas, producer > ), duration ) },
|
|
// Group potential
|
|
{ "GWPP", potential_rate< rt::well_potential_water , true, false>},
|
|
{ "GOPP", potential_rate< rt::well_potential_oil , true, false>},
|
|
{ "GGPP", potential_rate< rt::well_potential_gas , true, false>},
|
|
{ "GWPI", potential_rate< rt::well_potential_water , false, true>},
|
|
{ "GOPI", potential_rate< rt::well_potential_oil , false, true>},
|
|
{ "GGPI", potential_rate< rt::well_potential_gas , false, true>},
|
|
|
|
//Group control mode
|
|
{ "GMCTP", group_control< true, true, false, false >},
|
|
{ "GMCTW", group_control< true, false, true, false >},
|
|
{ "GMCTG", group_control< true, false, false, true >},
|
|
|
|
{ "WWPRH", production_history< Opm::Phase::WATER > },
|
|
{ "WOPRH", production_history< Opm::Phase::OIL > },
|
|
{ "WGPRH", production_history< Opm::Phase::GAS > },
|
|
{ "WLPRH", sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) },
|
|
|
|
{ "WWPTH", mul( production_history< Opm::Phase::WATER >, duration ) },
|
|
{ "WOPTH", mul( production_history< Opm::Phase::OIL >, duration ) },
|
|
{ "WGPTH", mul( production_history< Opm::Phase::GAS >, duration ) },
|
|
{ "WLPTH", mul( sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ),
|
|
duration ) },
|
|
|
|
{ "WWIRH", injection_history< Opm::Phase::WATER > },
|
|
{ "WOIRH", injection_history< Opm::Phase::OIL > },
|
|
{ "WGIRH", injection_history< Opm::Phase::GAS > },
|
|
{ "WWITH", mul( injection_history< Opm::Phase::WATER >, duration ) },
|
|
{ "WOITH", mul( injection_history< Opm::Phase::OIL >, duration ) },
|
|
{ "WGITH", mul( injection_history< Opm::Phase::GAS >, duration ) },
|
|
|
|
/* From our point of view, injectors don't have water cuts and div/sum will return 0.0 */
|
|
{ "WWCTH", div( production_history< Opm::Phase::WATER >,
|
|
sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) ) },
|
|
|
|
/* We do not support mixed injections, and gas/oil is undefined when oil is
|
|
* zero (i.e. pure gas injector), so always output 0 if this is an injector
|
|
*/
|
|
{ "WGORH", div( production_history< Opm::Phase::GAS >,
|
|
production_history< Opm::Phase::OIL > ) },
|
|
{ "WGLRH", div( production_history< Opm::Phase::GAS >,
|
|
sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) ) },
|
|
|
|
{ "WTHPH", thp_history },
|
|
{ "WBHPH", bhp_history },
|
|
|
|
{ "GWPRH", production_history< Opm::Phase::WATER > },
|
|
{ "GOPRH", production_history< Opm::Phase::OIL > },
|
|
{ "GGPRH", production_history< Opm::Phase::GAS > },
|
|
{ "GLPRH", sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) },
|
|
{ "GWIRH", injection_history< Opm::Phase::WATER > },
|
|
{ "GOIRH", injection_history< Opm::Phase::OIL > },
|
|
{ "GGIRH", injection_history< Opm::Phase::GAS > },
|
|
{ "GGORH", div( production_history< Opm::Phase::GAS >,
|
|
production_history< Opm::Phase::OIL > ) },
|
|
{ "GWCTH", div( production_history< Opm::Phase::WATER >,
|
|
sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) ) },
|
|
|
|
{ "GWPTH", mul( production_history< Opm::Phase::WATER >, duration ) },
|
|
{ "GOPTH", mul( production_history< Opm::Phase::OIL >, duration ) },
|
|
{ "GGPTH", mul( production_history< Opm::Phase::GAS >, duration ) },
|
|
{ "GGPRF", sub( rate < rt::gas, producer >, rate< rt::dissolved_gas, producer > )},
|
|
{ "GGPRS", rate< rt::dissolved_gas, producer> },
|
|
{ "GGPTF", mul( sub( rate < rt::gas, producer >, rate< rt::dissolved_gas, producer > ),
|
|
duration ) },
|
|
{ "GGPTS", mul( rate< rt::dissolved_gas, producer>, duration ) },
|
|
{ "GGLR", div( rate< rt::gas, producer >,
|
|
sum( rate< rt::wat, producer >,
|
|
rate< rt::oil, producer > ) ) },
|
|
{ "GGLRH", div( production_history< Opm::Phase::GAS >,
|
|
sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) ) },
|
|
{ "GLPTH", mul( sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ),
|
|
duration ) },
|
|
{ "GWITH", mul( injection_history< Opm::Phase::WATER >, duration ) },
|
|
{ "GGITH", mul( injection_history< Opm::Phase::GAS >, duration ) },
|
|
{ "GMWIN", flowing< injector > },
|
|
{ "GMWPR", flowing< producer > },
|
|
|
|
{ "GVPRT", res_vol_production_target },
|
|
|
|
{ "CPR", cpr },
|
|
{ "CGIRL", cratel< rt::gas, injector> },
|
|
{ "CGITL", mul( cratel< rt::gas, injector>, duration) },
|
|
{ "CWIRL", cratel< rt::wat, injector> },
|
|
{ "CWITL", mul( cratel< rt::wat, injector>, duration) },
|
|
{ "CWPRL", cratel< rt::wat, producer > },
|
|
{ "CWPTL", mul( cratel< rt::wat, producer >, duration) },
|
|
{ "COPRL", cratel< rt::oil, producer > },
|
|
{ "COPTL", mul( cratel< rt::oil, producer >, duration) },
|
|
{ "CGPRL", cratel< rt::gas, producer > },
|
|
{ "CGPTL", mul( cratel< rt::gas, producer >, duration) },
|
|
{ "COFRL", cratel< rt::oil, producer > },
|
|
{ "CGORL", div( cratel< rt::gas, producer >, cratel< rt::oil, producer > ) },
|
|
{ "CWCTL", div( cratel< rt::wat, producer >,
|
|
sum( cratel< rt::wat, producer >, cratel< rt::oil, producer > ) ) },
|
|
{ "CWIR", crate< rt::wat, injector > },
|
|
{ "CGIR", crate< rt::gas, injector > },
|
|
{ "COIR", crate< rt::oil, injector > },
|
|
{ "CVIR", crate_resv<injector> },
|
|
{ "CCIR", crate< rt::polymer, injector > },
|
|
{ "CSIR", crate< rt::brine, injector > },
|
|
{ "COIT", mul( crate< rt::oil, injector >, duration ) },
|
|
{ "CWIT", mul( crate< rt::wat, injector >, duration ) },
|
|
{ "CGIT", mul( crate< rt::gas, injector >, duration ) },
|
|
{ "CVIT", mul( crate_resv<injector>, duration ) },
|
|
{ "CNIT", mul( crate< rt::solvent, injector >, duration ) },
|
|
|
|
{ "CWPR", crate< rt::wat, producer > },
|
|
{ "COPR", crate< rt::oil, producer > },
|
|
{ "CGPR", crate< rt::gas, producer > },
|
|
{ "CVPR", crate_resv<producer> },
|
|
{ "CCPR", crate< rt::polymer, producer > },
|
|
{ "CSPR", crate< rt::brine, producer > },
|
|
{ "CGFR", sub(crate<rt::gas, producer>, crate<rt::gas, injector>) },
|
|
{ "COFR", sub(crate<rt::oil, producer>, crate<rt::oil, injector>) },
|
|
{ "CWFR", sub(crate<rt::wat, producer>, crate<rt::wat, injector>) },
|
|
{ "CWCT", div( crate< rt::wat, producer >,
|
|
sum( crate< rt::wat, producer >, crate< rt::oil, producer > ) ) },
|
|
{ "CGOR", div( crate< rt::gas, producer >, crate< rt::oil, producer > ) },
|
|
// Minus for injection rates and pluss for production rate
|
|
{ "CNFR", sub( crate< rt::solvent, producer >, crate<rt::solvent, injector >) },
|
|
{ "CWPT", mul( crate< rt::wat, producer >, duration ) },
|
|
{ "COPT", mul( crate< rt::oil, producer >, duration ) },
|
|
{ "CGPT", mul( crate< rt::gas, producer >, duration ) },
|
|
{ "CVPT", mul( crate_resv<producer>, duration ) },
|
|
{ "CNPT", mul( crate< rt::solvent, producer >, duration ) },
|
|
{ "CCIT", mul( crate< rt::polymer, injector >, duration ) },
|
|
{ "CCPT", mul( crate< rt::polymer, producer >, duration ) },
|
|
{ "CSIT", mul( crate< rt::brine, injector >, duration ) },
|
|
{ "CSPT", mul( crate< rt::brine, producer >, duration ) },
|
|
{ "CTFAC", trans_factors },
|
|
{ "CPI", connection_productivity_index },
|
|
|
|
{ "FWPR", rate< rt::wat, producer > },
|
|
{ "FOPR", rate< rt::oil, producer > },
|
|
{ "FGPR", rate< rt::gas, producer > },
|
|
{ "FEPR", rate< rt::energy, producer > },
|
|
{ "FTPRHEA", rate< rt::energy, producer > },
|
|
{ "FGLIR", glir },
|
|
{ "FNPR", rate< rt::solvent, producer > },
|
|
{ "FCPR", rate< rt::polymer, producer > },
|
|
{ "FSPR", rate< rt::brine, producer > },
|
|
{ "FCPC", div( rate< rt::polymer, producer >, rate< rt::wat, producer >) },
|
|
{ "FSPC", div( rate< rt::brine, producer >, rate< rt::wat, producer >) },
|
|
{ "FTPR#W", ratetracer< rt::tracer, rt::wat, producer > },
|
|
{ "FTPR#O", ratetracer< rt::tracer, rt::oil, producer > },
|
|
{ "FTPR#G", ratetracer< rt::tracer, rt::gas, producer > },
|
|
{ "FTPC#W", div( ratetracer< rt::tracer, rt::wat, producer >, rate< rt::wat, producer >) },
|
|
{ "FTPC#O", div( ratetracer< rt::tracer, rt::oil, producer >, rate< rt::oil, producer >) },
|
|
{ "FTPC#G", div( ratetracer< rt::tracer, rt::gas, producer >, rate< rt::gas, producer >) },
|
|
{ "FVPR", sum( sum( rate< rt::reservoir_water, producer>, rate< rt::reservoir_oil, producer >),
|
|
rate< rt::reservoir_gas, producer>)},
|
|
{ "FGPRS", rate< rt::dissolved_gas, producer > },
|
|
{ "FGPRF", sub( rate< rt::gas, producer >, rate< rt::dissolved_gas, producer > ) },
|
|
{ "FOPRS", rate< rt::vaporized_oil, producer > },
|
|
{ "FOPRF", sub (rate < rt::oil, producer >, rate< rt::vaporized_oil, producer > ) },
|
|
|
|
{ "FLPR", sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) },
|
|
{ "FWPT", mul( rate< rt::wat, producer >, duration ) },
|
|
{ "FOPT", mul( rate< rt::oil, producer >, duration ) },
|
|
{ "FGPT", mul( rate< rt::gas, producer >, duration ) },
|
|
{ "FEPT", mul( rate< rt::energy, producer >, duration ) },
|
|
{ "FTPTHEA", mul( rate< rt::energy, producer >, duration ) },
|
|
{ "FNPT", mul( rate< rt::solvent, producer >, duration ) },
|
|
{ "FCPT", mul( rate< rt::polymer, producer >, duration ) },
|
|
{ "FSPT", mul( rate< rt::brine, producer >, duration ) },
|
|
{ "FTPT#W", mul( ratetracer< rt::tracer, rt::wat, producer >, duration ) },
|
|
{ "FTPT#O", mul( ratetracer< rt::tracer, rt::oil, producer >, duration ) },
|
|
{ "FTPT#G", mul( ratetracer< rt::tracer, rt::gas, producer >, duration ) },
|
|
{ "FLPT", mul( sum( rate< rt::wat, producer >, rate< rt::oil, producer > ),
|
|
duration ) },
|
|
{ "FVPT", mul(sum (sum( rate< rt::reservoir_water, producer>, rate< rt::reservoir_oil, producer >),
|
|
rate< rt::reservoir_gas, producer>), duration)},
|
|
{ "FGPTS", mul( rate< rt::dissolved_gas, producer > , duration )},
|
|
{ "FGPTF", mul( sub( rate< rt::gas, producer >, rate< rt::dissolved_gas, producer > ), duration )},
|
|
{ "FOPTS", mul( rate< rt::vaporized_oil, producer >, duration ) },
|
|
{ "FOPTF", mul( sub (rate < rt::oil, producer >,
|
|
rate< rt::vaporized_oil, producer > ),
|
|
duration ) },
|
|
|
|
{ "FWIR", rate< rt::wat, injector > },
|
|
{ "FOIR", rate< rt::oil, injector > },
|
|
{ "FGIR", rate< rt::gas, injector > },
|
|
{ "FEIR", rate< rt::energy, injector > },
|
|
{ "FTIRHEA", rate< rt::energy, injector > },
|
|
{ "FNIR", rate< rt::solvent, injector > },
|
|
{ "FCIR", rate< rt::polymer, injector > },
|
|
{ "FSIR", rate< rt::brine, injector > },
|
|
{ "FTIR#W", ratetracer< rt::tracer, rt::wat, injector > },
|
|
{ "FTIR#O", ratetracer< rt::tracer, rt::oil, injector > },
|
|
{ "FTIR#G", ratetracer< rt::tracer, rt::gas, injector > },
|
|
{ "FTIC#W", div( ratetracer< rt::tracer, rt::wat, injector >, rate< rt::wat, injector >) },
|
|
{ "FTIC#O", div( ratetracer< rt::tracer, rt::oil, injector >, rate< rt::oil, injector >) },
|
|
{ "FTIC#G", div( ratetracer< rt::tracer, rt::gas, injector >, rate< rt::gas, injector >) },
|
|
{ "FVIR", sum( sum( rate< rt::reservoir_water, injector>, rate< rt::reservoir_oil, injector >),
|
|
rate< rt::reservoir_gas, injector>)},
|
|
|
|
{ "FLIR", sum( rate< rt::wat, injector >, rate< rt::oil, injector > ) },
|
|
{ "FWIT", mul( rate< rt::wat, injector >, duration ) },
|
|
{ "FOIT", mul( rate< rt::oil, injector >, duration ) },
|
|
{ "FGIT", mul( rate< rt::gas, injector >, duration ) },
|
|
{ "FEIT", mul( rate< rt::energy, injector >, duration ) },
|
|
{ "FTITHEA", mul( rate< rt::energy, injector >, duration ) },
|
|
{ "FNIT", mul( rate< rt::solvent, injector >, duration ) },
|
|
{ "FCIT", mul( rate< rt::polymer, injector >, duration ) },
|
|
{ "FSIT", mul( rate< rt::brine, injector >, duration ) },
|
|
{ "FTIT#W", mul( ratetracer< rt::tracer, rt::wat, injector >, duration ) },
|
|
{ "FTIT#O", mul( ratetracer< rt::tracer, rt::oil, injector >, duration ) },
|
|
{ "FTIT#G", mul( ratetracer< rt::tracer, rt::gas, injector >, duration ) },
|
|
{ "FLIT", mul( sum( rate< rt::wat, injector >, rate< rt::oil, injector > ),
|
|
duration ) },
|
|
{ "FVIT", mul( sum( sum( rate< rt::reservoir_water, injector>, rate< rt::reservoir_oil, injector >),
|
|
rate< rt::reservoir_gas, injector>), duration)},
|
|
// Field potential
|
|
{ "FWPP", potential_rate< rt::well_potential_water , true, false>},
|
|
{ "FOPP", potential_rate< rt::well_potential_oil , true, false>},
|
|
{ "FGPP", potential_rate< rt::well_potential_gas , true, false>},
|
|
{ "FWPI", potential_rate< rt::well_potential_water , false, true>},
|
|
{ "FOPI", potential_rate< rt::well_potential_oil , false, true>},
|
|
{ "FGPI", potential_rate< rt::well_potential_gas , false, true>},
|
|
|
|
|
|
{ "FWPRH", production_history< Opm::Phase::WATER > },
|
|
{ "FOPRH", production_history< Opm::Phase::OIL > },
|
|
{ "FGPRH", production_history< Opm::Phase::GAS > },
|
|
{ "FLPRH", sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) },
|
|
{ "FWPTH", mul( production_history< Opm::Phase::WATER >, duration ) },
|
|
{ "FOPTH", mul( production_history< Opm::Phase::OIL >, duration ) },
|
|
{ "FGPTH", mul( production_history< Opm::Phase::GAS >, duration ) },
|
|
{ "FLPTH", mul( sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ),
|
|
duration ) },
|
|
|
|
{ "FWIRH", injection_history< Opm::Phase::WATER > },
|
|
{ "FOIRH", injection_history< Opm::Phase::OIL > },
|
|
{ "FGIRH", injection_history< Opm::Phase::GAS > },
|
|
{ "FWITH", mul( injection_history< Opm::Phase::WATER >, duration ) },
|
|
{ "FOITH", mul( injection_history< Opm::Phase::OIL >, duration ) },
|
|
{ "FGITH", mul( injection_history< Opm::Phase::GAS >, duration ) },
|
|
|
|
{ "FWCT", div( rate< rt::wat, producer >,
|
|
sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) },
|
|
{ "FGOR", div( rate< rt::gas, producer >, rate< rt::oil, producer > ) },
|
|
{ "FGLR", div( rate< rt::gas, producer >,
|
|
sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) },
|
|
{ "FWCTH", div( production_history< Opm::Phase::WATER >,
|
|
sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) ) },
|
|
{ "FGORH", div( production_history< Opm::Phase::GAS >,
|
|
production_history< Opm::Phase::OIL > ) },
|
|
{ "FGLRH", div( production_history< Opm::Phase::GAS >,
|
|
sum( production_history< Opm::Phase::WATER >,
|
|
production_history< Opm::Phase::OIL > ) ) },
|
|
{ "FMWIN", flowing< injector > },
|
|
{ "FMWPR", flowing< producer > },
|
|
{ "FVPRT", res_vol_production_target },
|
|
{ "FMWPA", abandoned_well< producer > },
|
|
{ "FMWIA", abandoned_well< injector >},
|
|
|
|
//Field control mode
|
|
{ "FMCTP", group_control< false, true, false, false >},
|
|
{ "FMCTW", group_control< false, false, true, false >},
|
|
{ "FMCTG", group_control< false, false, false, true >},
|
|
|
|
/* Region properties */
|
|
{ "ROIR" , region_rate< rt::oil, injector > },
|
|
{ "RGIR" , region_rate< rt::gas, injector > },
|
|
{ "RWIR" , region_rate< rt::wat, injector > },
|
|
{ "ROPR" , region_rate< rt::oil, producer > },
|
|
{ "RGPR" , region_rate< rt::gas, producer > },
|
|
{ "RWPR" , region_rate< rt::wat, producer > },
|
|
{ "ROIT" , mul( region_rate< rt::oil, injector >, duration ) },
|
|
{ "RGIT" , mul( region_rate< rt::gas, injector >, duration ) },
|
|
{ "RWIT" , mul( region_rate< rt::wat, injector >, duration ) },
|
|
{ "ROPT" , mul( region_rate< rt::oil, producer >, duration ) },
|
|
{ "RGPT" , mul( region_rate< rt::gas, producer >, duration ) },
|
|
{ "RWPT" , mul( region_rate< rt::wat, producer >, duration ) },
|
|
{ "RHPV" , rhpv },
|
|
|
|
// Segment summary vectors for multi-segmented wells.
|
|
{ "SOFR" , srate<rt::oil> },
|
|
{ "SOFRF", sub(srate<rt::oil>, srate<rt::vaporized_oil>) }, // Free oil flow
|
|
{ "SOFRS", srate<rt::vaporized_oil> }, // Solution oil flow
|
|
{ "SGFR" , srate<rt::gas> },
|
|
{ "SGFRF", sub(srate<rt::gas>, srate<rt::dissolved_gas>) }, // Free gas flow
|
|
{ "SGFRS", srate<rt::dissolved_gas> }, // Solution gas flow
|
|
{ "SWFR" , srate<rt::wat> },
|
|
{ "SGOR" , div(srate<rt::gas>, srate<rt::oil>) },
|
|
{ "SOGR" , div(srate<rt::oil>, srate<rt::gas>) },
|
|
{ "SWCT" , div(srate<rt::wat>, sum(srate<rt::wat>, srate<rt::oil>)) },
|
|
{ "SWGR" , div(srate<rt::wat>, srate<rt::gas>) },
|
|
{ "SPR" , segpress<Opm::data::SegmentPressures::Value::Pressure> },
|
|
{ "SPRD" , segpress<Opm::data::SegmentPressures::Value::PDrop> },
|
|
{ "SPRDH", segpress<Opm::data::SegmentPressures::Value::PDropHydrostatic> },
|
|
{ "SPRDF", segpress<Opm::data::SegmentPressures::Value::PDropFriction> },
|
|
{ "SPRDA", segpress<Opm::data::SegmentPressures::Value::PDropAccel> },
|
|
|
|
// Well productivity index
|
|
{ "WPI", preferred_phase_productivty_index },
|
|
{ "WPIW", potential_rate< rt::productivity_index_water >},
|
|
{ "WPIO", potential_rate< rt::productivity_index_oil >},
|
|
{ "WPIG", potential_rate< rt::productivity_index_gas >},
|
|
{ "WPIL", sum( potential_rate< rt::productivity_index_water, true, false >,
|
|
potential_rate< rt::productivity_index_oil, true, false >)},
|
|
// Well potential
|
|
{ "WWPP", potential_rate< rt::well_potential_water , true, false>},
|
|
{ "WOPP", potential_rate< rt::well_potential_oil , true, false>},
|
|
{ "WGPP", potential_rate< rt::well_potential_gas , true, false>},
|
|
{ "WWPI", potential_rate< rt::well_potential_water , false, true>},
|
|
{ "WWIP", potential_rate< rt::well_potential_water , false, true>}, // Alias for 'WWPI'
|
|
{ "WOPI", potential_rate< rt::well_potential_oil , false, true>},
|
|
{ "WGPI", potential_rate< rt::well_potential_gas , false, true>},
|
|
{ "WGIP", potential_rate< rt::well_potential_gas , false, true>}, // Alias for 'WGPI'
|
|
{ "ROEW", roew },
|
|
|
|
// Efficiency factors
|
|
{"GEFF" , group_efficiency_factor},
|
|
{"WEFF" , well_efficiency_factor},
|
|
{"WEFFG", well_efficiency_factor_grouptree},
|
|
};
|
|
|
|
static const std::unordered_map< std::string, Opm::UnitSystem::measure> single_values_units = {
|
|
{"TCPU" , Opm::UnitSystem::measure::runtime },
|
|
{"ELAPSED" , Opm::UnitSystem::measure::identity },
|
|
{"NEWTON" , Opm::UnitSystem::measure::identity },
|
|
{"NLINERS" , Opm::UnitSystem::measure::identity },
|
|
{"NLINSMIN" , Opm::UnitSystem::measure::identity },
|
|
{"NLINSMAX" , Opm::UnitSystem::measure::identity },
|
|
{"MLINEARS" , Opm::UnitSystem::measure::identity },
|
|
{"NLINEARS" , Opm::UnitSystem::measure::identity },
|
|
{"MSUMLINS" , Opm::UnitSystem::measure::identity },
|
|
{"MSUMNEWT" , Opm::UnitSystem::measure::identity },
|
|
{"TCPUTS" , Opm::UnitSystem::measure::identity },
|
|
{"TIMESTEP" , Opm::UnitSystem::measure::time },
|
|
{"TCPUDAY" , Opm::UnitSystem::measure::time },
|
|
{"STEPTYPE" , Opm::UnitSystem::measure::identity },
|
|
{"TELAPLIN" , Opm::UnitSystem::measure::time },
|
|
{"FRPV" , Opm::UnitSystem::measure::volume },
|
|
{"FWIP" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"FWIPR" , Opm::UnitSystem::measure::volume },
|
|
{"FOIP" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"FOIPR" , Opm::UnitSystem::measure::volume },
|
|
{"FOE" , Opm::UnitSystem::measure::identity },
|
|
{"FGIP" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{"FGIPR" , Opm::UnitSystem::measure::volume },
|
|
{"FSIP" , Opm::UnitSystem::measure::mass },
|
|
{"FOIPL" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"FOIPG" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"FGIPL" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{"FGIPG" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{"FPR" , Opm::UnitSystem::measure::pressure },
|
|
|
|
};
|
|
|
|
static const std::unordered_map< std::string, Opm::UnitSystem::measure> region_units = {
|
|
{"RPR" , Opm::UnitSystem::measure::pressure},
|
|
{"RRPV" , Opm::UnitSystem::measure::volume },
|
|
{"ROIP" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"ROIPL" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"ROIPG" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"RGIP" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{"RGIPL" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{"RGIPG" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{"RWIP" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{"RRPV" , Opm::UnitSystem::measure::geometric_volume }
|
|
};
|
|
|
|
static const auto interregion_units =
|
|
std::unordered_map<std::string, Opm::UnitSystem::measure>
|
|
{
|
|
// Flow rates (surface volume)
|
|
{ "ROFR" , Opm::UnitSystem::measure::liquid_surface_rate },
|
|
{ "ROFR+" , Opm::UnitSystem::measure::liquid_surface_rate },
|
|
{ "ROFR-" , Opm::UnitSystem::measure::liquid_surface_rate },
|
|
{ "RGFR" , Opm::UnitSystem::measure::gas_surface_rate },
|
|
{ "RGFR+" , Opm::UnitSystem::measure::gas_surface_rate },
|
|
{ "RGFR-" , Opm::UnitSystem::measure::gas_surface_rate },
|
|
{ "RWFR" , Opm::UnitSystem::measure::liquid_surface_rate },
|
|
{ "RWFR+" , Opm::UnitSystem::measure::liquid_surface_rate },
|
|
{ "RWFR-" , Opm::UnitSystem::measure::liquid_surface_rate },
|
|
|
|
// Cumulatives (surface volume)
|
|
{ "ROFT" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "ROFT+" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "ROFT-" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "ROFTG" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "ROFTL" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "RGFT" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{ "RGFT+" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{ "RGFT-" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{ "RGFTG" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{ "RGFTL" , Opm::UnitSystem::measure::gas_surface_volume },
|
|
{ "RWFT" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "RWFT+" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
{ "RWFT-" , Opm::UnitSystem::measure::liquid_surface_volume },
|
|
};
|
|
|
|
static const std::unordered_map< std::string, Opm::UnitSystem::measure> block_units = {
|
|
{"BPR" , Opm::UnitSystem::measure::pressure},
|
|
{"BRPV" , Opm::UnitSystem::measure::volume },
|
|
{"BOPV" , Opm::UnitSystem::measure::volume },
|
|
{"BGPV" , Opm::UnitSystem::measure::volume },
|
|
{"BWPV" , Opm::UnitSystem::measure::volume },
|
|
{"BPRESSUR" , Opm::UnitSystem::measure::pressure},
|
|
{"BTCNFHEA" , Opm::UnitSystem::measure::temperature},
|
|
{"BTEMP" , Opm::UnitSystem::measure::temperature},
|
|
{"BSWAT" , Opm::UnitSystem::measure::identity},
|
|
{"BWSAT" , Opm::UnitSystem::measure::identity},
|
|
{"BSGAS" , Opm::UnitSystem::measure::identity},
|
|
{"BGSAT" , Opm::UnitSystem::measure::identity},
|
|
{"BSOIL" , Opm::UnitSystem::measure::identity},
|
|
{"BOSAT" , Opm::UnitSystem::measure::identity},
|
|
{"BNSAT" , Opm::UnitSystem::measure::identity},
|
|
{"BOIP" , Opm::UnitSystem::measure::liquid_surface_volume},
|
|
{"BOIPG" , Opm::UnitSystem::measure::liquid_surface_volume},
|
|
{"BOIPL" , Opm::UnitSystem::measure::liquid_surface_volume},
|
|
{"BGIP" , Opm::UnitSystem::measure::gas_surface_volume},
|
|
{"BGIPG" , Opm::UnitSystem::measure::gas_surface_volume},
|
|
{"BGIPL" , Opm::UnitSystem::measure::gas_surface_volume},
|
|
{"BWIP" , Opm::UnitSystem::measure::liquid_surface_volume},
|
|
{"BRS" , Opm::UnitSystem::measure::gas_oil_ratio},
|
|
{"BRV" , Opm::UnitSystem::measure::oil_gas_ratio},
|
|
{"BWKR" , Opm::UnitSystem::measure::identity},
|
|
{"BOKR" , Opm::UnitSystem::measure::identity},
|
|
{"BKRO" , Opm::UnitSystem::measure::identity},
|
|
{"BKROG" , Opm::UnitSystem::measure::identity},
|
|
{"BKROW" , Opm::UnitSystem::measure::identity},
|
|
{"BGKR" , Opm::UnitSystem::measure::identity},
|
|
{"BKRG" , Opm::UnitSystem::measure::identity},
|
|
{"BKRW" , Opm::UnitSystem::measure::identity},
|
|
{"BWPC" , Opm::UnitSystem::measure::pressure},
|
|
{"BWPR" , Opm::UnitSystem::measure::pressure},
|
|
{"BGPC" , Opm::UnitSystem::measure::pressure},
|
|
{"BGPR" , Opm::UnitSystem::measure::pressure},
|
|
{"BVWAT" , Opm::UnitSystem::measure::viscosity},
|
|
{"BWVIS" , Opm::UnitSystem::measure::viscosity},
|
|
{"BVGAS" , Opm::UnitSystem::measure::viscosity},
|
|
{"BGVIS" , Opm::UnitSystem::measure::viscosity},
|
|
{"BVOIL" , Opm::UnitSystem::measure::viscosity},
|
|
{"BOVIS" , Opm::UnitSystem::measure::viscosity},
|
|
};
|
|
|
|
static const std::unordered_map< std::string, Opm::UnitSystem::measure> aquifer_units = {
|
|
{"AAQT", Opm::UnitSystem::measure::liquid_surface_volume},
|
|
{"AAQR", Opm::UnitSystem::measure::liquid_surface_rate},
|
|
{"AAQP", Opm::UnitSystem::measure::pressure},
|
|
{"ANQP", Opm::UnitSystem::measure::pressure},
|
|
{"ANQT", Opm::UnitSystem::measure::liquid_surface_volume},
|
|
{"ANQR", Opm::UnitSystem::measure::liquid_surface_rate},
|
|
|
|
// Dimensionless time and pressure values for CT aquifers
|
|
{"AAQTD", Opm::UnitSystem::measure::identity},
|
|
{"AAQPD", Opm::UnitSystem::measure::identity},
|
|
};
|
|
|
|
void sort_wells_by_insert_index(std::vector<const Opm::Well*>& wells)
|
|
{
|
|
std::sort(wells.begin(), wells.end(),
|
|
[](const Opm::Well* w1, const Opm::Well* w2)
|
|
{
|
|
return w1->seqIndex() < w2->seqIndex();
|
|
});
|
|
}
|
|
|
|
std::vector<const Opm::Well*>
|
|
find_single_well(const Opm::Schedule& schedule,
|
|
const std::string& well_name,
|
|
const int sim_step)
|
|
{
|
|
auto single_well = std::vector<const Opm::Well*>{};
|
|
|
|
if (schedule.hasWell(well_name, sim_step)) {
|
|
single_well.push_back(&schedule.getWell(well_name, sim_step));
|
|
}
|
|
|
|
return single_well;
|
|
}
|
|
|
|
std::vector<const Opm::Well*>
|
|
find_region_wells(const Opm::Schedule& schedule,
|
|
const Opm::EclIO::SummaryNode& node,
|
|
const int sim_step,
|
|
const Opm::out::RegionCache& regionCache)
|
|
{
|
|
auto result = std::vector<const Opm::Well*>{};
|
|
auto regionwells = std::set<const Opm::Well*>{};
|
|
|
|
const auto region = node.number;
|
|
|
|
for (const auto& connection : regionCache.connections(*node.fip_region, region)) {
|
|
const auto& w_name = connection.first;
|
|
if (! schedule.hasWell(w_name, sim_step)) {
|
|
continue;
|
|
}
|
|
|
|
regionwells.insert(&schedule.getWell(w_name, sim_step));
|
|
}
|
|
|
|
result.assign(regionwells.begin(), regionwells.end());
|
|
sort_wells_by_insert_index(result);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<const Opm::Well*>
|
|
find_group_wells(const Opm::Schedule& schedule,
|
|
const std::string& group_name,
|
|
const int sim_step)
|
|
{
|
|
auto groupwells = std::vector<const Opm::Well*>{};
|
|
|
|
const auto& schedState = schedule[sim_step];
|
|
if (! schedState.groups.has(group_name)) {
|
|
return groupwells; // Empty
|
|
}
|
|
|
|
auto downtree = std::vector<std::string>{group_name};
|
|
for (auto i = 0*downtree.size(); i < downtree.size(); ++i) {
|
|
const auto& group = schedState.groups.get(downtree[i]);
|
|
|
|
if (group.wellgroup()) {
|
|
for (const auto& wname : group.wells()) {
|
|
groupwells.push_back(& schedState.wells.get(wname));
|
|
}
|
|
}
|
|
else {
|
|
const auto& children = group.groups();
|
|
downtree.insert(downtree.end(), children.begin(), children.end());
|
|
}
|
|
}
|
|
|
|
sort_wells_by_insert_index(groupwells);
|
|
|
|
return groupwells;
|
|
}
|
|
|
|
std::vector<const Opm::Well*>
|
|
find_field_wells(const Opm::Schedule& schedule,
|
|
const int sim_step)
|
|
{
|
|
auto fieldwells = std::vector<const Opm::Well*>{};
|
|
|
|
const auto& wells = schedule[sim_step].wells;
|
|
for (const auto& well : wells.keys()) {
|
|
fieldwells.push_back(&wells.get(well));
|
|
}
|
|
|
|
sort_wells_by_insert_index(fieldwells);
|
|
|
|
return fieldwells;
|
|
}
|
|
|
|
inline std::vector<const Opm::Well*>
|
|
find_wells(const Opm::Schedule& schedule,
|
|
const Opm::EclIO::SummaryNode& node,
|
|
const int sim_step,
|
|
const Opm::out::RegionCache& regionCache)
|
|
{
|
|
switch (node.category) {
|
|
case Opm::EclIO::SummaryNode::Category::Well:
|
|
case Opm::EclIO::SummaryNode::Category::Connection:
|
|
case Opm::EclIO::SummaryNode::Category::Completion:
|
|
case Opm::EclIO::SummaryNode::Category::Segment:
|
|
return find_single_well(schedule, node.wgname, sim_step);
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Group:
|
|
return find_group_wells(schedule, node.wgname, sim_step);
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Field:
|
|
return find_field_wells(schedule, sim_step);
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Region:
|
|
return find_region_wells(schedule, node, sim_step, regionCache);
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Aquifer:
|
|
case Opm::EclIO::SummaryNode::Category::Block:
|
|
case Opm::EclIO::SummaryNode::Category::Node:
|
|
case Opm::EclIO::SummaryNode::Category::Miscellaneous:
|
|
return {};
|
|
}
|
|
|
|
throw std::runtime_error {
|
|
fmt::format("Unhandled summary node category \"{}\" in find_wells()",
|
|
static_cast<int>(node.category))
|
|
};
|
|
}
|
|
|
|
bool need_wells(const Opm::EclIO::SummaryNode& node)
|
|
{
|
|
static const std::regex region_keyword_regex { "R[OGW][IP][RT]" };
|
|
static const std::regex group_guiderate_regex { "G[OGWV][IP]GR" };
|
|
|
|
using Cat = Opm::EclIO::SummaryNode::Category;
|
|
|
|
switch (node.category) {
|
|
case Cat::Connection: [[fallthrough]];
|
|
case Cat::Completion: [[fallthrough]];
|
|
case Cat::Field: [[fallthrough]];
|
|
case Cat::Group: [[fallthrough]];
|
|
case Cat::Segment: [[fallthrough]];
|
|
case Cat::Well:
|
|
// Need to capture wells for anything other than guiderates at group
|
|
// level. Those are directly available in the solution values from
|
|
// the simulator and don't need aggregation from well level.
|
|
return (node.category != Cat::Group)
|
|
|| !std::regex_match(node.keyword, group_guiderate_regex);
|
|
|
|
case Cat::Region:
|
|
return std::regex_match(node.keyword, region_keyword_regex);
|
|
|
|
case Cat::Aquifer: [[fallthrough]];
|
|
case Cat::Miscellaneous: [[fallthrough]];
|
|
case Cat::Node: [[fallthrough]];
|
|
// Node values directly available in solution.
|
|
case Cat::Block:
|
|
return false;
|
|
}
|
|
|
|
throw std::runtime_error {
|
|
fmt::format("Unhandled summary node category \"{}\" in need_wells()",
|
|
static_cast<int>(node.category))
|
|
};
|
|
}
|
|
|
|
void updateValue(const Opm::EclIO::SummaryNode& node, const double value, Opm::SummaryState& st)
|
|
{
|
|
using Cat = Opm::EclIO::SummaryNode::Category;
|
|
|
|
switch (node.category) {
|
|
case Cat::Well:
|
|
st.update_well_var(node.wgname, node.keyword, value);
|
|
break;
|
|
|
|
case Cat::Group:
|
|
case Cat::Node:
|
|
st.update_group_var(node.wgname, node.keyword, value);
|
|
break;
|
|
|
|
case Cat::Connection:
|
|
st.update_conn_var(node.wgname, node.keyword, node.number, value);
|
|
break;
|
|
|
|
case Cat::Segment:
|
|
st.update_segment_var(node.wgname, node.keyword, node.number, value);
|
|
break;
|
|
|
|
default:
|
|
st.update(node.unique_key(), value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* and open the well for short intervals within the same timestep, and the well
|
|
* is therefore solved at full speed.
|
|
*
|
|
* Groups are treated similarly as wells. The group's GEFAC is not applied for
|
|
* rates, only for accumulated volumes. When GEFAC is set for a group, it is
|
|
* considered that all wells are taken down simultaneously, and GEFAC is
|
|
* therefore not applied to the group's rate. However, any efficiency factors
|
|
* applied to the group's wells or sub-groups must be included.
|
|
*
|
|
* Regions and fields will have the well and group efficiency applied for both
|
|
* rates and accumulated values.
|
|
*
|
|
*/
|
|
struct EfficiencyFactor
|
|
{
|
|
using Factor = std::pair<std::string, double>;
|
|
using FacColl = std::vector<Factor>;
|
|
|
|
FacColl factors{};
|
|
|
|
void setFactors(const Opm::EclIO::SummaryNode& node,
|
|
const Opm::Schedule& schedule,
|
|
const std::vector<const Opm::Well*>& schedule_wells,
|
|
const int sim_step);
|
|
};
|
|
|
|
void EfficiencyFactor::setFactors(const Opm::EclIO::SummaryNode& node,
|
|
const Opm::Schedule& schedule,
|
|
const std::vector<const Opm::Well*>& schedule_wells,
|
|
const int sim_step)
|
|
{
|
|
this->factors.clear();
|
|
|
|
const bool is_field { node.category == Opm::EclIO::SummaryNode::Category::Field } ;
|
|
const bool is_group { node.category == Opm::EclIO::SummaryNode::Category::Group } ;
|
|
const bool is_region { node.category == Opm::EclIO::SummaryNode::Category::Region } ;
|
|
const bool is_rate { node.type != Opm::EclIO::SummaryNode::Type::Total } ;
|
|
|
|
if (!is_field && !is_group && !is_region && is_rate)
|
|
return;
|
|
|
|
for (const auto* well : schedule_wells) {
|
|
if (!well->hasBeenDefined(sim_step))
|
|
continue;
|
|
|
|
double eff_factor = well->getEfficiencyFactor();
|
|
const auto* group_ptr = std::addressof(schedule.getGroup(well->groupName(), sim_step));
|
|
|
|
while (group_ptr) {
|
|
if (is_group && is_rate && (group_ptr->name() == node.wgname))
|
|
break;
|
|
|
|
eff_factor *= group_ptr->getGroupEfficiencyFactor();
|
|
|
|
const auto parent_group = group_ptr->flow_group();
|
|
|
|
if (parent_group.has_value())
|
|
group_ptr = std::addressof(schedule.getGroup( parent_group.value(), sim_step ));
|
|
else
|
|
group_ptr = nullptr;
|
|
}
|
|
|
|
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;
|
|
const Opm::Inplace initial_inplace;
|
|
};
|
|
|
|
struct SimulatorResults
|
|
{
|
|
const Opm::data::Wells& wellSol;
|
|
const Opm::data::GroupAndNetworkValues& grpNwrkSol;
|
|
const std::map<std::string, double>& single;
|
|
const Opm::Inplace inplace;
|
|
const std::map<std::string, std::vector<double>>& region;
|
|
const std::map<std::pair<std::string, int>, double>& block;
|
|
const Opm::data::Aquifers& aquifers;
|
|
const std::unordered_map<std::string, Opm::data::InterRegFlowMap>& ireg;
|
|
};
|
|
|
|
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::EclIO::SummaryNode node, ofun fcn)
|
|
: node_(std::move(node))
|
|
, fcn_ (std::move(fcn))
|
|
{
|
|
if (this->use_number()) {
|
|
this->number_ = this->node_.number;
|
|
}
|
|
}
|
|
|
|
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_);
|
|
|
|
const auto wells = get_wells
|
|
? find_wells(input.sched, this->node_,
|
|
static_cast<int>(sim_step), input.reg)
|
|
: std::vector<const Opm::Well*>{};
|
|
|
|
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, this->group_name(), this->node_.keyword, stepSize, static_cast<int>(sim_step),
|
|
std::max(0, this->number_),
|
|
this->node_.fip_region,
|
|
st, simRes.wellSol, simRes.grpNwrkSol,
|
|
input.reg, input.grid, input.sched,
|
|
std::move(efac.factors), input.initial_inplace, simRes.inplace,
|
|
input.sched.getUnits()
|
|
};
|
|
|
|
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::EclIO::SummaryNode node_;
|
|
ofun fcn_;
|
|
int number_{0};
|
|
|
|
std::string group_name() const
|
|
{
|
|
using Cat = ::Opm::EclIO::SummaryNode::Category;
|
|
|
|
const auto need_grp_name =
|
|
(this->node_.category == Cat::Group) ||
|
|
(this->node_.category == Cat::Node);
|
|
|
|
return need_grp_name
|
|
? this->node_.wgname : std::string{""};
|
|
}
|
|
|
|
bool use_number() const
|
|
{
|
|
using Cat = ::Opm::EclIO::SummaryNode::Category;
|
|
const auto cat = this->node_.category;
|
|
|
|
return ! ((cat == Cat::Well) ||
|
|
(cat == Cat::Group) ||
|
|
(cat == Cat::Field) ||
|
|
(cat == Cat::Node) ||
|
|
(cat == Cat::Miscellaneous));
|
|
}
|
|
};
|
|
|
|
class BlockValue : public Base
|
|
{
|
|
public:
|
|
explicit BlockValue(Opm::EclIO::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::EclIO::SummaryNode node_;
|
|
Opm::UnitSystem::measure m_;
|
|
|
|
Opm::out::Summary::BlockValues::key_type lookupKey() const
|
|
{
|
|
return { this->node_.keyword, this->node_.number };
|
|
}
|
|
};
|
|
|
|
class AquiferValue: public Base
|
|
{
|
|
public:
|
|
explicit AquiferValue(Opm::EclIO::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.aquifers.find(this->node_.number);
|
|
if (xPos == simRes.aquifers.end()) {
|
|
return;
|
|
}
|
|
|
|
const auto& usys = input.es.getUnits();
|
|
updateValue(this->node_, usys.from_si(this->m_, xPos->second.get(this->node_.keyword)), st);
|
|
}
|
|
private:
|
|
Opm::EclIO::SummaryNode node_;
|
|
Opm::UnitSystem::measure m_;
|
|
};
|
|
|
|
class RegionValue : public Base
|
|
{
|
|
public:
|
|
explicit RegionValue(Opm::EclIO::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::EclIO::SummaryNode node_;
|
|
Opm::UnitSystem::measure m_;
|
|
|
|
std::vector<double>::size_type index() const
|
|
{
|
|
return this->node_.number - 1;
|
|
}
|
|
};
|
|
|
|
class InterRegionValue : public Base
|
|
{
|
|
public:
|
|
explicit InterRegionValue(const Opm::EclIO::SummaryNode& node,
|
|
const Opm::UnitSystem::measure m)
|
|
: node_ (node)
|
|
, m_ (m)
|
|
, regname_(node_.fip_region.has_value()
|
|
? node_.fip_region.value()
|
|
: std::string{ "FIPNUM" })
|
|
{
|
|
this->analyzeKeyword();
|
|
}
|
|
|
|
void update(const std::size_t /* sim_step */,
|
|
const double stepSize,
|
|
const InputData& input,
|
|
const SimulatorResults& simRes,
|
|
Opm::SummaryState& st) const override
|
|
{
|
|
if (this->component_ == Component::NumComponents) {
|
|
return;
|
|
}
|
|
|
|
auto flows = simRes.ireg.find(this->regname_);
|
|
if (flows == simRes.ireg.end()) {
|
|
return;
|
|
}
|
|
|
|
auto flow = flows->second.getInterRegFlows(this->r1_, this->r2_);
|
|
if (! flow.has_value()) {
|
|
return;
|
|
}
|
|
|
|
const auto& usys = input.es.getUnits();
|
|
const auto val = this->getValue(flow->first, flow->second, stepSize);
|
|
|
|
updateValue(this->node_, usys.from_si(this->m_, val), st);
|
|
}
|
|
|
|
private:
|
|
using RateWindow = Opm::data::InterRegFlowMap::ReadOnlyWindow;
|
|
using Component = RateWindow::Component;
|
|
using Direction = RateWindow::Direction;
|
|
|
|
Opm::EclIO::SummaryNode node_;
|
|
Opm::UnitSystem::measure m_;
|
|
std::string regname_{};
|
|
|
|
Component component_{ Component::NumComponents };
|
|
Component subtract_ { Component::NumComponents };
|
|
Direction direction_{ Direction::Positive };
|
|
bool useDirection_{ false };
|
|
bool isCumulative_{ false };
|
|
int r1_{ -1 };
|
|
int r2_{ -1 };
|
|
|
|
void analyzeKeyword()
|
|
{
|
|
// Valid keywords are
|
|
//
|
|
// - R[OGW]F[TR]
|
|
// Basic oil/gas/water flow rates and cumulatives. FIPNUM
|
|
// region set.
|
|
//
|
|
// - R[OGW]F[TR][-+]
|
|
// Directional versions of basic oil/gas/water flow rates
|
|
// and cumulatives. FIPNUM region set.
|
|
//
|
|
// - R[OG]F[TR][GL]
|
|
// Flow rates and cumulatives of free oil (ROF[TR]L),
|
|
// vaporised oil (ROF[TR]G), free gas (RGF[TR]G), and gas
|
|
// dissolved in liquid (RGF[TR]L). FIPNUM region set.
|
|
//
|
|
// - R[OGW]F[TR]_[A-Z0-9]{3}
|
|
// Basic oil/gas/water flow rates and cumulatives. User
|
|
// defined region set (FIP* keyword).
|
|
//
|
|
// - R[OGW]F[TR][-+][A-Z0-9]{3}
|
|
// Directional versions of basic oil/gas/water flow rates
|
|
// and cumulatives. User defined region set (FIP* keyword).
|
|
//
|
|
// - R[OG]F[TR][GL][A-Z0-9]{3}
|
|
// Flow rates and cumulatives of free oil (ROF[TR]L),
|
|
// vaporised oil (ROF[TR]G), free gas (RGF[TR]G), and gas
|
|
// dissolved in liquid (RGF[TR]L). User defined region set
|
|
// (FIP* keyword).
|
|
//
|
|
// We don't need a full keyword verification here, only to
|
|
// extract the pertinent keyword pieces, because the input
|
|
// keyword validity is enforced at the parser level. See json
|
|
// descriptions REGION2REGION_PROBE and REGION2REGION_PROBE_E300
|
|
// in input/eclipse/share/keywords.
|
|
//
|
|
// Note that we explicitly disregard the region set name here as
|
|
// this name does not influence the interpretation of the
|
|
// summary vector keyword-only the definition of the individual
|
|
// regions.
|
|
|
|
static const auto pattern = std::regex {
|
|
R"~~(R([OGW])F([RT])([-+GL]?)(?:_?[A-Z0-9_]{3})?)~~"
|
|
};
|
|
|
|
auto keywordPieces = std::smatch {};
|
|
if (std::regex_match(this->node_.keyword, keywordPieces, pattern)) {
|
|
this->identifyComponent(keywordPieces);
|
|
this->identifyDirection(keywordPieces);
|
|
this->identifyCumulative(keywordPieces);
|
|
this->assignRegionIDs();
|
|
}
|
|
}
|
|
|
|
double getValue(const RateWindow& iregFlow,
|
|
const double sign,
|
|
const double stepSize) const
|
|
{
|
|
const auto prim = this->useDirection_
|
|
? iregFlow.flow(this->component_, this->direction_)
|
|
: iregFlow.flow(this->component_);
|
|
|
|
const auto sub = (this->subtract_ == Component::NumComponents)
|
|
? 0.0 : iregFlow.flow(this->subtract_);
|
|
|
|
const auto val = sign * (prim - sub);
|
|
|
|
return this->isCumulative_
|
|
? stepSize * val
|
|
: val;
|
|
}
|
|
|
|
void assignRegionIDs()
|
|
{
|
|
const auto& [r1, r2] =
|
|
Opm::EclIO::splitSummaryNumber(this->node_.number);
|
|
|
|
this->r1_ = r1 - 1;
|
|
this->r2_ = r2 - 1;
|
|
}
|
|
|
|
void identifyComponent(const std::smatch& keywordPieces)
|
|
{
|
|
const auto main = keywordPieces[1].str();
|
|
|
|
if (main == "O") {
|
|
this->component_ = (keywordPieces[3].str() == "G")
|
|
? Component::Vapoil
|
|
: Component::Oil;
|
|
|
|
if (keywordPieces[3].str() == "L") {
|
|
// Free oil = "oil - vapoil"
|
|
this->subtract_ = Component::Vapoil;
|
|
}
|
|
}
|
|
else if (main == "G") {
|
|
this->component_ = (keywordPieces[3].str() == "L")
|
|
? Component::Disgas
|
|
: Component::Gas;
|
|
|
|
if (keywordPieces[3].str() == "G") {
|
|
// Free gas = "gas - disgas"
|
|
this->subtract_ = Component::Disgas;
|
|
}
|
|
}
|
|
else if (main == "W") {
|
|
this->component_ = Component::Water;
|
|
}
|
|
}
|
|
|
|
void identifyDirection(const std::smatch& keywordPieces)
|
|
{
|
|
if (keywordPieces.length(3) == std::smatch::difference_type{0}) {
|
|
return;
|
|
}
|
|
|
|
const auto dir = keywordPieces[3].str();
|
|
|
|
this->useDirection_ = (dir == "+") || (dir == "-");
|
|
|
|
if (dir == "-") {
|
|
this->direction_ = Direction::Negative;
|
|
}
|
|
}
|
|
|
|
void identifyCumulative(const std::smatch& keywordPieces)
|
|
{
|
|
assert (keywordPieces.length(2) != std::smatch::difference_type{0});
|
|
|
|
const auto type = keywordPieces[2].str();
|
|
|
|
this->isCumulative_ = type == "T";
|
|
}
|
|
};
|
|
|
|
class GlobalProcessValue : public Base
|
|
{
|
|
public:
|
|
explicit GlobalProcessValue(Opm::EclIO::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::EclIO::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));
|
|
st.update("TIME", usys.from_si(m, val));
|
|
}
|
|
|
|
private:
|
|
std::string saveKey_;
|
|
};
|
|
|
|
class Day : public Base
|
|
{
|
|
public:
|
|
explicit Day(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
|
|
{
|
|
auto sim_time = make_sim_time(input.sched, st, stepSize);
|
|
st.update(this->saveKey_, sim_time.day());
|
|
}
|
|
|
|
private:
|
|
std::string saveKey_;
|
|
};
|
|
|
|
class Month : public Base
|
|
{
|
|
public:
|
|
explicit Month(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
|
|
{
|
|
auto sim_time = make_sim_time(input.sched, st, stepSize);
|
|
st.update(this->saveKey_, sim_time.month());
|
|
}
|
|
|
|
private:
|
|
std::string saveKey_;
|
|
};
|
|
|
|
class Year : public Base
|
|
{
|
|
public:
|
|
explicit Year(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
|
|
{
|
|
auto sim_time = make_sim_time(input.sched, st, stepSize);
|
|
st.update(this->saveKey_, sim_time.year());
|
|
}
|
|
|
|
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, ecl_year));
|
|
}
|
|
|
|
private:
|
|
std::string saveKey_;
|
|
};
|
|
|
|
class Factory
|
|
{
|
|
public:
|
|
struct Descriptor
|
|
{
|
|
std::string uniquekey{};
|
|
std::string unit{};
|
|
std::unique_ptr<Base> evaluator{};
|
|
};
|
|
|
|
explicit Factory(const Opm::EclipseState& es,
|
|
const Opm::EclipseGrid& grid,
|
|
const Opm::Schedule& sched,
|
|
const Opm::SummaryState& st,
|
|
const Opm::UDQConfig& udq)
|
|
: es_(es), sched_(sched), 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::EclIO::SummaryNode&);
|
|
|
|
private:
|
|
const Opm::EclipseState& es_;
|
|
const Opm::Schedule& sched_;
|
|
const Opm::EclipseGrid& grid_;
|
|
const Opm::SummaryState& st_;
|
|
const Opm::UDQConfig& udq_;
|
|
|
|
const Opm::EclIO::SummaryNode* node_;
|
|
|
|
Opm::UnitSystem::measure paramUnit_;
|
|
ofun paramFunction_;
|
|
|
|
Descriptor functionRelation();
|
|
Descriptor blockValue();
|
|
Descriptor aquiferValue();
|
|
Descriptor regionValue();
|
|
Descriptor interRegionValue();
|
|
Descriptor globalProcessValue();
|
|
Descriptor userDefinedValue();
|
|
Descriptor unknownParameter();
|
|
|
|
bool isBlockValue();
|
|
bool isAquiferValue();
|
|
bool isRegionValue();
|
|
bool isInterRegionValue();
|
|
bool isGlobalProcessValue();
|
|
bool isFunctionRelation();
|
|
bool isUserDefined();
|
|
|
|
std::string functionUnitString() const;
|
|
std::string directUnitString() const;
|
|
std::string userDefinedUnit() const;
|
|
};
|
|
|
|
Factory::Descriptor Factory::create(const Opm::EclIO::SummaryNode& node)
|
|
{
|
|
this->node_ = &node;
|
|
|
|
if (this->isUserDefined())
|
|
return this->userDefinedValue();
|
|
|
|
if (this->isBlockValue())
|
|
return this->blockValue();
|
|
|
|
if (this->isAquiferValue())
|
|
return this->aquiferValue();
|
|
|
|
if (this->isRegionValue())
|
|
return this->regionValue();
|
|
|
|
if (this->isInterRegionValue())
|
|
return this->interRegionValue();
|
|
|
|
if (this->isGlobalProcessValue())
|
|
return this->globalProcessValue();
|
|
|
|
if (this->isFunctionRelation())
|
|
return this->functionRelation();
|
|
|
|
return this->unknownParameter();
|
|
}
|
|
|
|
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::aquiferValue()
|
|
{
|
|
auto desc = this->unknownParameter();
|
|
|
|
desc.unit = this->directUnitString();
|
|
desc.evaluator.reset(new AquiferValue {
|
|
*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::interRegionValue()
|
|
{
|
|
auto desc = this->unknownParameter();
|
|
|
|
desc.unit = this->directUnitString();
|
|
desc.evaluator.reset(new InterRegionValue {
|
|
*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_->unique_key();
|
|
|
|
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::isAquiferValue()
|
|
{
|
|
auto pos = aquifer_units.find(this->node_->keyword);
|
|
if (pos == aquifer_units.end()) return false;
|
|
|
|
// if the aquifer does not exist, should we warn?
|
|
if ( !this->es_.aquifer().hasAquifer(this->node_->number) &&
|
|
!this->sched_.hasAquiferFlux(this->node_->number)) return false;
|
|
|
|
this->paramUnit_ = pos->second;
|
|
return true;
|
|
}
|
|
|
|
bool Factory::isRegionValue()
|
|
{
|
|
auto keyword = this->node_->keyword;
|
|
auto dash_pos = keyword.find("_");
|
|
if (dash_pos != std::string::npos)
|
|
keyword = keyword.substr(0, dash_pos);
|
|
|
|
auto pos = region_units.find(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::isInterRegionValue()
|
|
{
|
|
const auto end = std::min({
|
|
// Infinity (std::string::npos) if no underscore
|
|
this->node_->keyword.find('_'),
|
|
|
|
// Don't look beyond end of keyword string.
|
|
this->node_->keyword.size(),
|
|
|
|
// Always at most 5 characters in the "real" keyword.
|
|
std::string::size_type{5},
|
|
});
|
|
|
|
auto pos = interregion_units.find(this->node_->keyword.substr(0, end));
|
|
if (pos == interregion_units.end()) {
|
|
// Node_'s canonical form reduced keyword does not match any of
|
|
// the supported inter-region flow summary vector keywords.
|
|
return false;
|
|
}
|
|
|
|
// 'Node_' represents a supported inter-region summary vector.
|
|
// 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()
|
|
{
|
|
const auto normKw = Opm::EclIO::SummaryNode::
|
|
normalise_keyword(this->node_->category, this->node_->keyword);
|
|
|
|
auto pos = funs.find(normKw);
|
|
if (pos != funs.end()) {
|
|
// 'node_' represents a functional relation.
|
|
// Capture evaluation function and return true.
|
|
this->paramFunction_ = pos->second;
|
|
return true;
|
|
}
|
|
|
|
if (normKw.length() <= std::string::size_type{4}) {
|
|
return false;
|
|
}
|
|
|
|
const auto tracer_name = normKw.substr(4);
|
|
|
|
const auto& tracers = this->es_.tracer();
|
|
auto trPos = std::find_if(tracers.begin(), tracers.end(),
|
|
[&tracer_name](const auto& tracer)
|
|
{
|
|
return tracer.name == tracer_name;
|
|
});
|
|
|
|
if (trPos == tracers.end()) {
|
|
return false;
|
|
}
|
|
|
|
auto tracer_tag = normKw.substr(0, 4);
|
|
switch (trPos->phase) {
|
|
case Opm::Phase::WATER:
|
|
tracer_tag += "#W";
|
|
break;
|
|
|
|
case Opm::Phase::OIL:
|
|
tracer_tag += "#O";
|
|
break;
|
|
|
|
case Opm::Phase::GAS:
|
|
tracer_tag += "#G";
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
pos = funs.find(tracer_tag);
|
|
if (pos != funs.end()) {
|
|
this->paramFunction_ = pos->second;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Factory::isUserDefined()
|
|
{
|
|
return this->node_->is_user_defined();
|
|
}
|
|
|
|
std::string Factory::functionUnitString() const
|
|
{
|
|
const auto reg = Opm::out::RegionCache{};
|
|
|
|
const fn_args args {
|
|
{}, "", this->node_->keyword, 0.0, 0, std::max(0, this->node_->number),
|
|
this->node_->fip_region,
|
|
this->st_, {}, {}, reg, this->grid_, this->sched_,
|
|
{}, {}, {}, Opm::UnitSystem(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC)
|
|
};
|
|
|
|
const auto prm = this->paramFunction_(args);
|
|
|
|
std::string unit_string_tracer = this->es_.tracer().get_unit_string(this->es_.getUnits(), this->node_->keyword);
|
|
if (unit_string_tracer != "") { //Non-default unit for tracer amount.
|
|
return unit_string_tracer;
|
|
}
|
|
|
|
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<Opm::SummaryConfigNode> keywords)
|
|
{
|
|
// Sort by location first, then keyword.
|
|
auto loc_kw_ordering = [](const Opm::SummaryConfigNode& n1, const Opm::SummaryConfigNode& n2) {
|
|
if (n1.location() == n2.location()) {
|
|
return n1.keyword() < n2.keyword();
|
|
}
|
|
if (n1.location().filename == n2.location().filename) {
|
|
return n1.location().lineno < n2.location().lineno;
|
|
}
|
|
return n1.location().filename < n2.location().filename;
|
|
};
|
|
std::sort(keywords.begin(), keywords.end(), loc_kw_ordering);
|
|
|
|
// Reorder to remove duplicate { keyword, location } pairs, since
|
|
// that will give duplicate and therefore useless warnings.
|
|
auto same_kw_and_loc = [](const Opm::SummaryConfigNode& n1, const Opm::SummaryConfigNode& n2) {
|
|
return (n1.keyword() == n2.keyword()) && (n1.location() == n2.location());
|
|
};
|
|
auto uend = std::unique(keywords.begin(), keywords.end(), same_kw_and_loc);
|
|
|
|
for (auto node = keywords.begin(); node != uend; ++node) {
|
|
const auto& location = node->location();
|
|
Opm::OpmLog::warning(Opm::OpmInputError::format("Unhandled summary keyword {keyword}\n"
|
|
"In {file} line {line}", location));
|
|
}
|
|
}
|
|
|
|
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.
|
|
|
|
const auto use_dflt = name.empty() ||
|
|
(name.find_first_not_of(" \t") == std::string::npos);
|
|
|
|
return use_dflt ? std::string(":+:+:+:+") : std::move(name);
|
|
}
|
|
|
|
class SummaryOutputParameters
|
|
{
|
|
public:
|
|
using EvalPtr = std::unique_ptr<Evaluator::Base>;
|
|
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<EvalPtr>& getEvaluators() const
|
|
{
|
|
return this->evaluators_;
|
|
}
|
|
|
|
private:
|
|
SMSpecPrm smspec_{};
|
|
std::vector<EvalPtr> evaluators_{};
|
|
};
|
|
|
|
class SMSpecStreamDeferredCreation
|
|
{
|
|
private:
|
|
using Spec = ::Opm::EclIO::OutputStream::SummarySpecification;
|
|
|
|
public:
|
|
using ResultSet = ::Opm::EclIO::OutputStream::ResultSet;
|
|
using Formatted = ::Opm::EclIO::OutputStream::Formatted;
|
|
|
|
explicit SMSpecStreamDeferredCreation(const Opm::InitConfig& initcfg,
|
|
const Opm::EclipseGrid& grid,
|
|
const std::time_t start,
|
|
const Opm::UnitSystem::UnitType utype);
|
|
|
|
std::unique_ptr<Spec>
|
|
createStream(const ResultSet& rset, const Formatted& fmt) const
|
|
{
|
|
return std::make_unique<Spec>(rset, fmt, this->uconv(),
|
|
this->cartDims_, this->restart_,
|
|
this->start_);
|
|
}
|
|
|
|
private:
|
|
Opm::UnitSystem::UnitType utype_;
|
|
std::array<int,3> cartDims_;
|
|
Spec::StartTime start_;
|
|
Spec::RestartSpecification restart_{};
|
|
|
|
Spec::UnitConvention uconv() const;
|
|
};
|
|
|
|
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_ (Opm::TimeService::from_time_t(start))
|
|
{
|
|
if (initcfg.restartRequested()) {
|
|
this->restart_.root = initcfg.getRestartRootName();
|
|
this->restart_.step = initcfg.getRestartStep();
|
|
}
|
|
}
|
|
|
|
SMSpecStreamDeferredCreation::Spec::UnitConvention
|
|
SMSpecStreamDeferredCreation::uconv() const
|
|
{
|
|
using UType = ::Opm::UnitSystem::UnitType;
|
|
|
|
if (this->utype_ == UType::UNIT_TYPE_METRIC)
|
|
return Spec::UnitConvention::Metric;
|
|
|
|
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<int>(this->utype_)) + ')'
|
|
};
|
|
}
|
|
|
|
std::unique_ptr<SMSpecStreamDeferredCreation>
|
|
makeDeferredSMSpecCreation(const Opm::EclipseState& es,
|
|
const Opm::EclipseGrid& grid,
|
|
const Opm::Schedule& sched)
|
|
{
|
|
return std::make_unique<SMSpecStreamDeferredCreation>
|
|
(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<unsigned char>(c));
|
|
c = static_cast<std::string::value_type>(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 };
|
|
}
|
|
|
|
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 {
|
|
fmt::format("Elapsed time ({} {}) "
|
|
"must not precede previous elapsed time ({} {}). "
|
|
"Incorrect restart time?", elapsed, unt, prev_el, unt)
|
|
};
|
|
}
|
|
|
|
} // 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,
|
|
const bool writeEsmry);
|
|
|
|
SummaryImplementation(const SummaryImplementation& rhs) = delete;
|
|
SummaryImplementation(SummaryImplementation&& rhs) = default;
|
|
SummaryImplementation& operator=(const SummaryImplementation& rhs) = delete;
|
|
SummaryImplementation& operator=(SummaryImplementation&& rhs) = default;
|
|
|
|
void eval(const int sim_step,
|
|
const double secs_elapsed,
|
|
const data::Wells& well_solution,
|
|
const data::GroupAndNetworkValues& grp_nwrk_solution,
|
|
GlobalProcessParameters& single_values,
|
|
const Inplace& initial_inplace,
|
|
const Opm::Inplace& inplace,
|
|
const RegionParameters& region_values,
|
|
const BlockValues& block_values,
|
|
const data::Aquifers& aquifer_values,
|
|
const InterRegFlowValues& interreg_flows,
|
|
SummaryState& st) const;
|
|
|
|
void internal_store(const SummaryState& st, const int report_step, bool isSubstep);
|
|
void write(const bool is_final_summary);
|
|
PAvgCalculatorCollection wbp_calculators(std::size_t report_step) const;
|
|
|
|
private:
|
|
struct MiniStep
|
|
{
|
|
int id{0};
|
|
int seq{-1};
|
|
bool isSubstep{false};
|
|
std::vector<float> params{};
|
|
};
|
|
|
|
using EvalPtr = SummaryOutputParameters::EvalPtr;
|
|
|
|
std::reference_wrapper<const Opm::EclipseGrid> grid_;
|
|
std::reference_wrapper<const Opm::EclipseState> es_;
|
|
std::reference_wrapper<const Opm::Schedule> sched_;
|
|
Opm::out::RegionCache regCache_;
|
|
std::unordered_set<std::string> wbp_wells;
|
|
|
|
std::unique_ptr<SMSpecStreamDeferredCreation> deferredSMSpec_;
|
|
|
|
Opm::EclIO::OutputStream::ResultSet rset_;
|
|
Opm::EclIO::OutputStream::Formatted fmt_;
|
|
Opm::EclIO::OutputStream::Unified unif_;
|
|
|
|
mutable int miniStepID_{0};
|
|
mutable double prevEvalTime_{std::numeric_limits<double>::lowest()};
|
|
|
|
int prevCreate_{-1};
|
|
int prevReportStepID_{-1};
|
|
std::vector<MiniStep>::size_type numUnwritten_{0};
|
|
|
|
SummaryOutputParameters outputParameters_{};
|
|
std::unordered_map<std::string, EvalPtr> extra_parameters{};
|
|
std::vector<std::string> valueKeys_{};
|
|
std::vector<std::string> valueUnits_{};
|
|
std::vector<MiniStep> unwritten_{};
|
|
|
|
std::unique_ptr<Opm::EclIO::OutputStream::SummarySpecification> smspec_{};
|
|
std::unique_ptr<Opm::EclIO::EclOutput> stream_{};
|
|
|
|
std::unique_ptr<Opm::EclIO::ExtSmryOutput> esmry_;
|
|
|
|
void configureTimeVector(const EclipseState& es, const std::string& kw);
|
|
void configureTimeVectors(const EclipseState& es, const SummaryConfig& sumcfg);
|
|
|
|
void configureSummaryInput(const SummaryConfig& sumcfg,
|
|
Evaluator::Factory& evaluatorFactory);
|
|
|
|
void configureRequiredRestartParameters(const SummaryConfig& sumcfg,
|
|
const AquiferConfig& aqConfig,
|
|
const Schedule& sched,
|
|
Evaluator::Factory& evaluatorFactory);
|
|
|
|
void configureUDQ(const EclipseState& es, const SummaryConfig& summary_config, const Schedule& sched);
|
|
|
|
MiniStep& getNextMiniStep(const int report_step, bool isSubstep);
|
|
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,
|
|
const bool writeEsmry)
|
|
: grid_ (std::cref(grid))
|
|
, es_ (std::cref(es))
|
|
, sched_ (std::cref(sched))
|
|
, regCache_ (sumcfg.fip_regions(), es.globalFieldProps(), grid, sched)
|
|
, deferredSMSpec_(makeDeferredSMSpecCreation(es, grid, sched))
|
|
, rset_ (makeResultSet(es.cfg().io(), basename))
|
|
, fmt_ { es.cfg().io().getFMTOUT() }
|
|
, unif_ { es.cfg().io().getUNIFOUT() }
|
|
{
|
|
const auto st = SummaryState {
|
|
TimeService::from_time_t(sched.getStartTime())
|
|
};
|
|
|
|
Evaluator::Factory evaluatorFactory {
|
|
es, grid, sched, st, sched.getUDQConfig(sched.size() - 1)
|
|
};
|
|
|
|
this->configureTimeVectors(es, sumcfg);
|
|
this->configureSummaryInput(sumcfg, evaluatorFactory);
|
|
this->configureRequiredRestartParameters(sumcfg, es.aquifer(),
|
|
sched, evaluatorFactory);
|
|
this->configureUDQ(es, sumcfg, sched);
|
|
|
|
for (const auto& config_node : sumcfg.keywords("WBP*"))
|
|
this->wbp_wells.insert( config_node.namedEntity() );
|
|
|
|
std::string esmryFileName = EclIO::OutputStream::outputFileName(this->rset_, "ESMRY");
|
|
|
|
if (std::filesystem::exists(esmryFileName))
|
|
std::filesystem::remove(esmryFileName);
|
|
|
|
if ((writeEsmry) and (es.cfg().io().getFMTOUT()==false))
|
|
this->esmry_ = std::make_unique<Opm::EclIO::ExtSmryOutput>(this->valueKeys_, this->valueUnits_, es, sched.posixStartTime());
|
|
|
|
if ((writeEsmry) and (es.cfg().io().getFMTOUT()))
|
|
OpmLog::warning("ESMRY only supported for unformatted output. Request ignored.");
|
|
}
|
|
|
|
void Opm::out::Summary::SummaryImplementation::
|
|
internal_store(const SummaryState& st, const int report_step, bool isSubstep)
|
|
{
|
|
auto& ms = this->getNextMiniStep(report_step, isSubstep);
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
Opm::PAvgCalculatorCollection
|
|
Opm::out::Summary::SummaryImplementation::wbp_calculators(std::size_t report_step) const
|
|
{
|
|
if (this->wbp_wells.empty())
|
|
return {};
|
|
|
|
Opm::PAvgCalculatorCollection calculators;
|
|
const auto& porv = this->es_.get().globalFieldProps().porv(true);
|
|
for (const auto& wname : this->wbp_wells) {
|
|
if (this->sched_.get().hasWell(wname, report_step)) {
|
|
const auto& well = this->sched_.get().getWell(wname, report_step);
|
|
if (well.getStatus() == Opm::Well::Status::OPEN)
|
|
calculators.add(well.pavg_calculator(this->grid_, porv));
|
|
}
|
|
}
|
|
|
|
return calculators;
|
|
}
|
|
|
|
void
|
|
Opm::out::Summary::SummaryImplementation::
|
|
eval(const int sim_step,
|
|
const double secs_elapsed,
|
|
const data::Wells& well_solution,
|
|
const data::GroupAndNetworkValues& grp_nwrk_solution,
|
|
GlobalProcessParameters& single_values,
|
|
const Inplace& initial_inplace,
|
|
const Opm::Inplace& inplace,
|
|
const RegionParameters& region_values,
|
|
const BlockValues& block_values,
|
|
const data::Aquifers& aquifer_values,
|
|
const InterRegFlowValues& interreg_flows,
|
|
Opm::SummaryState& st) const
|
|
{
|
|
validateElapsedTime(secs_elapsed, this->es_, st);
|
|
|
|
const double duration = secs_elapsed - st.get_elapsed();
|
|
single_values["TIMESTEP"] = duration;
|
|
st.update("TIMESTEP", this->es_.get().getUnits().from_si(Opm::UnitSystem::measure::time, duration));
|
|
|
|
const Evaluator::InputData input {
|
|
this->es_, this->sched_, this->grid_, this->regCache_, initial_inplace
|
|
};
|
|
|
|
const Evaluator::SimulatorResults simRes {
|
|
well_solution, grp_nwrk_solution, single_values, inplace,
|
|
region_values, block_values, aquifer_values, interreg_flows
|
|
};
|
|
|
|
for (auto& evalPtr : this->outputParameters_.getEvaluators()) {
|
|
evalPtr->update(sim_step, duration, input, simRes, st);
|
|
}
|
|
|
|
for (auto& [_, evalPtr] : this->extra_parameters) {
|
|
(void)_;
|
|
evalPtr->update(sim_step, duration, input, simRes, st);
|
|
}
|
|
|
|
st.update_elapsed(duration);
|
|
|
|
if (secs_elapsed > this->prevEvalTime_) {
|
|
this->prevEvalTime_ = secs_elapsed;
|
|
++this->miniStepID_;
|
|
}
|
|
}
|
|
|
|
void Opm::out::Summary::SummaryImplementation::write(const bool is_final_summary)
|
|
{
|
|
const auto zero = std::vector<MiniStep>::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();
|
|
|
|
if (this->esmry_ != nullptr){
|
|
for (auto i = 0*this->numUnwritten_; i < this->numUnwritten_; ++i){
|
|
this->esmry_->write(this->unwritten_[i].params, !this->unwritten_[i].isSubstep, is_final_summary);
|
|
}
|
|
}
|
|
|
|
// 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<int>{ ms.seq });
|
|
this->prevReportStepID_ = ms.seq;
|
|
}
|
|
|
|
this->stream_->write("MINISTEP", std::vector<int>{ ms.id });
|
|
this->stream_->write("PARAMS" , ms.params);
|
|
}
|
|
|
|
void
|
|
Opm::out::Summary::SummaryImplementation::
|
|
configureTimeVector(const EclipseState& es, const std::string& kw) {
|
|
const auto dfltwgname = std::string(":+:+:+:+");
|
|
const auto dfltnum = 0;
|
|
|
|
this->valueKeys_.push_back(kw);
|
|
|
|
if (kw == "TIME") {
|
|
const std::string& unit_string = es.getUnits().name(UnitSystem::measure::time);
|
|
auto eval = std::make_unique<Evaluator::Time>(kw);
|
|
|
|
valueUnits_.push_back(unit_string);
|
|
|
|
|
|
this->outputParameters_
|
|
.makeParameter(kw, dfltwgname, dfltnum, unit_string, std::move(eval));
|
|
}
|
|
|
|
else if (kw == "DAY") {
|
|
auto eval = std::make_unique<Evaluator::Day>(kw);
|
|
valueUnits_.push_back("");
|
|
|
|
this->outputParameters_
|
|
.makeParameter(kw, dfltwgname, dfltnum, "", std::move(eval));
|
|
}
|
|
|
|
else if (kw == "MONTH" || kw == "MNTH") {
|
|
auto eval = std::make_unique<Evaluator::Month>(kw);
|
|
valueUnits_.push_back("");
|
|
|
|
this->outputParameters_
|
|
.makeParameter(kw, dfltwgname, dfltnum, "", std::move(eval));
|
|
}
|
|
|
|
else if (kw == "YEAR") {
|
|
auto eval = std::make_unique<Evaluator::Year>(kw);
|
|
valueUnits_.push_back("");
|
|
this->outputParameters_
|
|
.makeParameter(kw, dfltwgname, dfltnum, "", std::move(eval));
|
|
}
|
|
|
|
else if (kw == "YEARS") {
|
|
auto eval = std::make_unique<Evaluator::Years>(kw);
|
|
valueUnits_.push_back("");
|
|
|
|
this->outputParameters_
|
|
.makeParameter(kw, dfltwgname, dfltnum, kw, std::move(eval));
|
|
}
|
|
}
|
|
|
|
void
|
|
Opm::out::Summary::SummaryImplementation::
|
|
configureTimeVectors(const EclipseState& es, const SummaryConfig& sumcfg)
|
|
{
|
|
this->configureTimeVector(es, "TIME");
|
|
this->configureTimeVector(es, "YEARS");
|
|
|
|
if (sumcfg.hasKeyword("DAY"))
|
|
this->configureTimeVector(es, "DAY");
|
|
|
|
if (sumcfg.hasKeyword("MONTH"))
|
|
this->configureTimeVector(es, "MONTH");
|
|
|
|
if (sumcfg.hasKeyword("YEAR"))
|
|
this->configureTimeVector(es, "YEAR");
|
|
}
|
|
|
|
void
|
|
Opm::out::Summary::SummaryImplementation::
|
|
configureSummaryInput(const SummaryConfig& sumcfg,
|
|
Evaluator::Factory& evaluatorFactory)
|
|
{
|
|
auto unsuppkw = std::vector<SummaryConfigNode>{};
|
|
for (const auto& node : sumcfg) {
|
|
auto prmDescr = evaluatorFactory.create(node);
|
|
|
|
if (! prmDescr.evaluator) {
|
|
// No known evaluation function/type for this keyword
|
|
unsuppkw.push_back(node);
|
|
continue;
|
|
}
|
|
|
|
// This keyword has a known evaluation method.
|
|
|
|
this->valueKeys_.push_back(std::move(prmDescr.uniquekey));
|
|
this->valueUnits_.push_back(prmDescr.unit);
|
|
|
|
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));
|
|
}
|
|
|
|
/*
|
|
These nodes are added to the summary evaluation list because they are
|
|
requested by the UDQ system. In the case of well and group variables the code
|
|
will all nodes for all wells / groups - irrespective of what has been
|
|
requested in the UDQ code.
|
|
*/
|
|
|
|
namespace {
|
|
std::vector<Opm::EclIO::SummaryNode>
|
|
make_default_nodes(const std::string& keyword,
|
|
const Opm::Schedule& sched)
|
|
{
|
|
if (Opm::TimeService::valid_month(keyword))
|
|
return {};
|
|
|
|
auto nodes = std::vector<Opm::EclIO::SummaryNode> {};
|
|
auto category = Opm::parseKeywordCategory(keyword);
|
|
auto type = Opm::parseKeywordType(keyword);
|
|
|
|
switch (category) {
|
|
case Opm::EclIO::SummaryNode::Category::Field:
|
|
{
|
|
Opm::EclIO::SummaryNode node;
|
|
node.keyword = keyword;
|
|
node.category = category;
|
|
node.type = type;
|
|
|
|
nodes.push_back(node);
|
|
}
|
|
break;
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Miscellaneous:
|
|
{
|
|
Opm::EclIO::SummaryNode node;
|
|
node.keyword = keyword;
|
|
node.category = category;
|
|
node.type = type;
|
|
|
|
nodes.push_back(node);
|
|
}
|
|
break;
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Well:
|
|
{
|
|
for (const auto& well : sched.wellNames()) {
|
|
Opm::EclIO::SummaryNode node;
|
|
node.keyword = keyword;
|
|
node.category = category;
|
|
node.type = type;
|
|
node.wgname = well;
|
|
|
|
nodes.push_back(node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Opm::EclIO::SummaryNode::Category::Group:
|
|
{
|
|
for (const auto& group : sched.groupNames()) {
|
|
Opm::EclIO::SummaryNode node;
|
|
node.keyword = keyword;
|
|
node.category = category;
|
|
node.type = type;
|
|
node.wgname = group;
|
|
|
|
nodes.push_back(node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw std::logic_error {
|
|
fmt::format("Keyword category '{}' (e.g., summary keyword {}) is not supported in ACTIONX",
|
|
category, keyword)
|
|
};
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
}
|
|
|
|
void Opm::out::Summary::SummaryImplementation::configureUDQ(const EclipseState& es,
|
|
const SummaryConfig& summary_config,
|
|
const Schedule& sched)
|
|
{
|
|
const std::unordered_set<std::string> time_vectors = {"TIME", "DAY", "MONTH", "YEAR", "YEARS", "MNTH"};
|
|
auto nodes = std::vector<Opm::EclIO::SummaryNode> {};
|
|
std::unordered_set<std::string> summary_keys;
|
|
for (const auto& [_, udq] : sched.unique<UDQConfig>()) {
|
|
(void)_;
|
|
udq.required_summary(summary_keys);
|
|
}
|
|
|
|
for (const auto& action : sched.back().actions.get())
|
|
action.required_summary(summary_keys);
|
|
|
|
for (const auto& key : summary_keys) {
|
|
const auto& default_nodes = make_default_nodes(key, sched);
|
|
for (const auto& def_node : default_nodes)
|
|
nodes.push_back(def_node);
|
|
}
|
|
|
|
for (const auto& node: nodes) {
|
|
// Handler already configured/requested through the normal
|
|
// SummaryConfig path.
|
|
if (summary_config.hasSummaryKey(node.unique_key()))
|
|
continue;
|
|
|
|
// Time related vectors are special cased in the valueKeys_ vector
|
|
// and must be checked explicitly.
|
|
if (time_vectors.count(node.keyword) > 0) {
|
|
this->configureTimeVector(es, node.keyword);
|
|
continue;
|
|
}
|
|
|
|
// Handler already registered in the summary evaluator, in some
|
|
// other way.
|
|
if (std::find(this->valueKeys_.begin(), this->valueKeys_.end(), node.unique_key()) != this->valueKeys_.end())
|
|
continue;
|
|
|
|
auto fun_pos = funs.find(node.keyword);
|
|
if (fun_pos != funs.end()) {
|
|
this->extra_parameters.emplace(node.unique_key(), std::make_unique<Evaluator::FunctionRelation>(node, fun_pos->second));
|
|
continue;
|
|
}
|
|
|
|
auto unit = single_values_units.find(node.keyword);
|
|
if (unit != single_values_units.end()) {
|
|
this->extra_parameters.emplace(node.unique_key(), std::make_unique<Evaluator::GlobalProcessValue>(node, unit->second));
|
|
continue;
|
|
}
|
|
|
|
if (node.is_user_defined())
|
|
continue;
|
|
|
|
throw std::logic_error {
|
|
fmt::format("Evaluation function for: {} not found ", node.keyword)
|
|
};
|
|
}
|
|
}
|
|
|
|
void
|
|
Opm::out::Summary::SummaryImplementation::
|
|
configureRequiredRestartParameters(const SummaryConfig& sumcfg,
|
|
const AquiferConfig& aqConfig,
|
|
const Schedule& sched,
|
|
Evaluator::Factory& evaluatorFactory)
|
|
{
|
|
auto makeEvaluator = [&sumcfg, &evaluatorFactory, this]
|
|
(const Opm::EclIO::SummaryNode& node) -> void
|
|
{
|
|
if (sumcfg.hasSummaryKey(node.unique_key()))
|
|
// Handler already exists. Don't add second evaluation.
|
|
return;
|
|
|
|
auto descriptor = evaluatorFactory.create(node);
|
|
if (descriptor.evaluator == nullptr)
|
|
throw std::logic_error {
|
|
fmt::format("Evaluation function for:{} not found", node.keyword)
|
|
};
|
|
|
|
this->extra_parameters
|
|
.emplace(node.unique_key(), std::move(descriptor.evaluator));
|
|
};
|
|
|
|
for (const auto& node : requiredRestartVectors(sched))
|
|
makeEvaluator(node);
|
|
|
|
for (const auto& node : requiredSegmentVectors(sched))
|
|
makeEvaluator(node);
|
|
|
|
const auto& aquflux_ids = sched.getAquiferFluxListEnd();
|
|
if (aqConfig.hasAnalyticalAquifer() || !aquflux_ids.empty()) {
|
|
auto aquiferIDs = analyticAquiferIDs(aqConfig);
|
|
if ( !aquflux_ids.empty() ) {
|
|
aquiferIDs.insert(aquiferIDs.end(), aquflux_ids.begin(), aquflux_ids.end());
|
|
}
|
|
|
|
for (const auto& node : requiredAquiferVectors(aquiferIDs))
|
|
makeEvaluator(node);
|
|
}
|
|
|
|
if (aqConfig.hasNumericalAquifer()) {
|
|
const auto aquiferIDs = numericAquiferIDs(aqConfig);
|
|
|
|
for (const auto& node : requiredNumericAquiferVectors(aquiferIDs))
|
|
makeEvaluator(node);
|
|
}
|
|
}
|
|
|
|
Opm::out::Summary::SummaryImplementation::MiniStep&
|
|
Opm::out::Summary::SummaryImplementation::getNextMiniStep(const int report_step, bool isSubstep)
|
|
{
|
|
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_ - 1; // MINISTEP IDs start at zero.
|
|
ms.seq = report_step;
|
|
ms.isSubstep = isSubstep;
|
|
|
|
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 Opm { namespace out {
|
|
|
|
Summary::Summary(const EclipseState& es,
|
|
const SummaryConfig& sumcfg,
|
|
const EclipseGrid& grid,
|
|
const Schedule& sched,
|
|
const std::string& basename,
|
|
const bool writeEsmry)
|
|
: pImpl_(new SummaryImplementation(es, sumcfg, grid, sched, basename, writeEsmry))
|
|
{}
|
|
|
|
void Summary::eval(SummaryState& st,
|
|
const int report_step,
|
|
const double secs_elapsed,
|
|
const data::Wells& well_solution,
|
|
const data::GroupAndNetworkValues& grp_nwrk_solution,
|
|
GlobalProcessParameters single_values,
|
|
const Inplace& initial_inplace,
|
|
const Inplace& inplace,
|
|
const PAvgCalculatorCollection& ,
|
|
const RegionParameters& region_values,
|
|
const BlockValues& block_values,
|
|
const Opm::data::Aquifers& aquifer_values,
|
|
const InterRegFlowValues& interreg_flows) const
|
|
{
|
|
// Report_step is the one-based sequence number of the containing report.
|
|
// Report_step = 0 for the initial condition, before simulation starts.
|
|
// We typically don't get reports_step = 0 here. When outputting
|
|
// separate summary files 'report_step' is the number that gets
|
|
// incorporated into the filename extension.
|
|
//
|
|
// Sim_step is the timestep which has been effective in the simulator,
|
|
// and as such is the value necessary to use when looking up active
|
|
// wells, groups, connections &c in the Schedule object.
|
|
const auto sim_step = std::max(0, report_step - 1);
|
|
|
|
this->pImpl_->eval(sim_step, secs_elapsed,
|
|
well_solution, grp_nwrk_solution, single_values,
|
|
initial_inplace, inplace,
|
|
region_values, block_values,
|
|
aquifer_values, interreg_flows, st);
|
|
}
|
|
|
|
PAvgCalculatorCollection
|
|
Summary::wbp_calculators(std::size_t report_step) const
|
|
{
|
|
return this->pImpl_->wbp_calculators(report_step);
|
|
}
|
|
|
|
void Summary::add_timestep(const SummaryState& st, const int report_step, bool isSubstep)
|
|
{
|
|
this->pImpl_->internal_store(st, report_step, isSubstep);
|
|
}
|
|
|
|
void Summary::write(const bool is_final_summary) const
|
|
{
|
|
this->pImpl_->write(is_final_summary);
|
|
}
|
|
|
|
Summary::~Summary() {}
|
|
|
|
}} // namespace Opm::out
|