mirror of
https://github.com/OPM/ResInsight.git
synced 2026-11-13 09:37:29 -06:00
315 lines
13 KiB
C++
315 lines
13 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|||
|
|
//
|
||
|
|
// Copyright (C) 2026- Equinor ASA
|
||
|
|
//
|
||
|
|
// ResInsight 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.
|
||
|
|
//
|
||
|
|
// ResInsight 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 at <http://www.gnu.org/licenses/gpl.html>
|
||
|
|
// for more details.
|
||
|
|
//
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
|
|
||
|
|
#include "RifGroupNetworkSummaryData.h"
|
||
|
|
|
||
|
|
#include "RifSummaryReaderInterface.h"
|
||
|
|
|
||
|
|
#include <algorithm>
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
const std::string fieldNodeName = "FIELD";
|
||
|
|
|
||
|
|
// WSTAT values, see the Eclipse reference manual
|
||
|
|
constexpr int wstatProducer = 1;
|
||
|
|
constexpr int wstatFirstInjector = 2;
|
||
|
|
constexpr int wstatLastInjector = 4;
|
||
|
|
const std::string wellStatusVectorName = "WSTAT";
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
bool RifGroupNetworkSummaryData::NodeCategory::isOther() const
|
||
|
|
{
|
||
|
|
return !isProducer && !isInjector;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
RifGroupNetworkSummaryData::RifGroupNetworkSummaryData( ReaderProvider readerProvider, RigGroupNetwork::TreeType treeType )
|
||
|
|
: m_readerProvider( std::move( readerProvider ) )
|
||
|
|
, m_treeType( treeType )
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
const std::vector<std::time_t>& RifGroupNetworkSummaryData::timeSteps() const
|
||
|
|
{
|
||
|
|
reader();
|
||
|
|
return m_timeSteps;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
/// The current reader. The serial number identifies the reader object, so a replaced reader is detected even if it is
|
||
|
|
/// allocated at the address of the previous one.
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
const RifSummaryReaderInterface* RifGroupNetworkSummaryData::reader() const
|
||
|
|
{
|
||
|
|
RifSummaryReaderInterface* currentReader = m_readerProvider ? m_readerProvider() : nullptr;
|
||
|
|
const int serialNumber = currentReader ? currentReader->serialNumber() : -1;
|
||
|
|
if ( serialNumber == m_readerSerialNumber ) return currentReader;
|
||
|
|
|
||
|
|
m_readerSerialNumber = serialNumber;
|
||
|
|
m_cache.clear();
|
||
|
|
m_nodeUnits.clear();
|
||
|
|
m_timeSteps.clear();
|
||
|
|
|
||
|
|
if ( currentReader )
|
||
|
|
{
|
||
|
|
currentReader->createAddressesIfRequired();
|
||
|
|
|
||
|
|
const auto& addresses = currentReader->allResultAddresses();
|
||
|
|
if ( !addresses.empty() )
|
||
|
|
{
|
||
|
|
const auto fopr = RifEclipseSummaryAddress::fieldAddress( "FOPR" );
|
||
|
|
m_timeSteps = currentReader->timeSteps( addresses.contains( fopr ) ? fopr : *addresses.begin() );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return currentReader;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::optional<double>
|
||
|
|
RifGroupNetworkSummaryData::edgeValue( const std::string& nodeName, bool isWell, EdgeMetric metric, size_t timeStepIndex ) const
|
||
|
|
{
|
||
|
|
return valueAt( edgeAddress( nodeName, isWell, metric ), timeStepIndex );
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::optional<double>
|
||
|
|
RifGroupNetworkSummaryData::nodeValue( const std::string& nodeName, bool isWell, NodeMetric metric, size_t timeStepIndex ) const
|
||
|
|
{
|
||
|
|
return valueAt( nodeAddress( nodeName, isWell, metric ), timeStepIndex );
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::string RifGroupNetworkSummaryData::edgeUnit( EdgeMetric metric ) const
|
||
|
|
{
|
||
|
|
const auto* summaryReader = reader();
|
||
|
|
if ( !summaryReader ) return {};
|
||
|
|
return summaryReader->unitName( edgeAddress( fieldNodeName, false, metric ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
/// The unit of the first node with the vector. The result is cached, as all summary addresses are searched.
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::string RifGroupNetworkSummaryData::nodeUnit( NodeMetric metric ) const
|
||
|
|
{
|
||
|
|
const auto* summaryReader = reader();
|
||
|
|
if ( !summaryReader ) return {};
|
||
|
|
|
||
|
|
if ( auto it = m_nodeUnits.find( metric ); it != m_nodeUnits.end() ) return it->second;
|
||
|
|
|
||
|
|
std::string unit;
|
||
|
|
const auto wellAddress = nodeAddress( "", true, metric );
|
||
|
|
const auto groupAddress = nodeAddress( "", false, metric );
|
||
|
|
for ( const auto& address : summaryReader->allResultAddresses() )
|
||
|
|
{
|
||
|
|
if ( address.vectorName() == wellAddress.vectorName() || address.vectorName() == groupAddress.vectorName() )
|
||
|
|
{
|
||
|
|
unit = summaryReader->unitName( address );
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
m_nodeUnits[metric] = unit;
|
||
|
|
return unit;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
RifGroupNetworkSummaryData::NodeCategory RifGroupNetworkSummaryData::category( const std::string& nodeName, bool isWell ) const
|
||
|
|
{
|
||
|
|
NodeCategory result;
|
||
|
|
|
||
|
|
if ( isWell )
|
||
|
|
{
|
||
|
|
if ( const auto* status = values( address( wellStatusVectorName, nodeName, true ) ) )
|
||
|
|
{
|
||
|
|
for ( double value : *status )
|
||
|
|
{
|
||
|
|
const int wstat = static_cast<int>( value );
|
||
|
|
if ( wstat == wstatProducer ) result.isProducer = true;
|
||
|
|
if ( wstat >= wstatFirstInjector && wstat <= wstatLastInjector ) result.isInjector = true;
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for ( auto metric : { EdgeMetric::OIL_RATE, EdgeMetric::GAS_RATE, EdgeMetric::WATER_RATE } )
|
||
|
|
{
|
||
|
|
if ( hasPositiveValue( edgeAddress( nodeName, isWell, metric ) ) ) result.isProducer = true;
|
||
|
|
}
|
||
|
|
for ( auto metric : { EdgeMetric::WATER_INJ_RATE, EdgeMetric::GAS_INJ_RATE } )
|
||
|
|
{
|
||
|
|
if ( hasPositiveValue( edgeAddress( nodeName, isWell, metric ) ) ) result.isInjector = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
RifEclipseSummaryAddress RifGroupNetworkSummaryData::edgeAddress( const std::string& nodeName, bool isWell, EdgeMetric metric ) const
|
||
|
|
{
|
||
|
|
const bool extended = m_treeType == RigGroupNetwork::TreeType::BRANPROP;
|
||
|
|
const std::string prefix = isWell ? "W" : ( nodeName == fieldNodeName ? "F" : "G" );
|
||
|
|
|
||
|
|
// The extended network uses the network rates of the groups. Injection rates are kept, as nodes may also
|
||
|
|
// be in an injection network (GNETINJE).
|
||
|
|
const std::string networkSuffix = ( extended && prefix == "G" ) ? "NB" : "";
|
||
|
|
|
||
|
|
switch ( metric )
|
||
|
|
{
|
||
|
|
case EdgeMetric::OIL_RATE:
|
||
|
|
return address( prefix + "OPR" + networkSuffix, nodeName, isWell );
|
||
|
|
case EdgeMetric::GAS_RATE:
|
||
|
|
return address( prefix + "GPR" + networkSuffix, nodeName, isWell );
|
||
|
|
case EdgeMetric::WATER_RATE:
|
||
|
|
return address( prefix + "WPR" + networkSuffix, nodeName, isWell );
|
||
|
|
case EdgeMetric::WATER_INJ_RATE:
|
||
|
|
return address( prefix + "WIR", nodeName, isWell );
|
||
|
|
case EdgeMetric::GAS_INJ_RATE:
|
||
|
|
return address( prefix + "GIR", nodeName, isWell );
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
RifEclipseSummaryAddress RifGroupNetworkSummaryData::nodeAddress( const std::string& nodeName, bool isWell, NodeMetric metric ) const
|
||
|
|
{
|
||
|
|
switch ( metric )
|
||
|
|
{
|
||
|
|
case NodeMetric::PRESSURE:
|
||
|
|
return isWell ? RifEclipseSummaryAddress::wellAddress( "WTHP", nodeName )
|
||
|
|
: RifEclipseSummaryAddress::groupAddress( "GPR", nodeName );
|
||
|
|
case NodeMetric::BHP:
|
||
|
|
return isWell ? RifEclipseSummaryAddress::wellAddress( "WBHP", nodeName ) : RifEclipseSummaryAddress();
|
||
|
|
case NodeMetric::CONTROL_MODE:
|
||
|
|
return isWell ? RifEclipseSummaryAddress::wellAddress( "WMCTL", nodeName ) : RifEclipseSummaryAddress();
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::string RifGroupNetworkSummaryData::edgeMetricText( EdgeMetric metric )
|
||
|
|
{
|
||
|
|
switch ( metric )
|
||
|
|
{
|
||
|
|
case EdgeMetric::OIL_RATE:
|
||
|
|
return "Oil Rate";
|
||
|
|
case EdgeMetric::GAS_RATE:
|
||
|
|
return "Gas Rate";
|
||
|
|
case EdgeMetric::WATER_RATE:
|
||
|
|
return "Water Rate";
|
||
|
|
case EdgeMetric::WATER_INJ_RATE:
|
||
|
|
return "Water Injection Rate";
|
||
|
|
case EdgeMetric::GAS_INJ_RATE:
|
||
|
|
return "Gas Injection Rate";
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::string RifGroupNetworkSummaryData::nodeMetricText( NodeMetric metric )
|
||
|
|
{
|
||
|
|
switch ( metric )
|
||
|
|
{
|
||
|
|
case NodeMetric::PRESSURE:
|
||
|
|
return "Pressure";
|
||
|
|
case NodeMetric::BHP:
|
||
|
|
return "Bottom Hole Pressure";
|
||
|
|
case NodeMetric::CONTROL_MODE:
|
||
|
|
return "Control Mode";
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
const std::vector<double>* RifGroupNetworkSummaryData::values( const RifEclipseSummaryAddress& address ) const
|
||
|
|
{
|
||
|
|
const auto* summaryReader = reader();
|
||
|
|
if ( !summaryReader || !address.isValid() ) return nullptr;
|
||
|
|
|
||
|
|
auto it = m_cache.find( address );
|
||
|
|
if ( it == m_cache.end() )
|
||
|
|
{
|
||
|
|
std::optional<std::vector<double>> cached;
|
||
|
|
if ( summaryReader->hasAddress( address ) )
|
||
|
|
{
|
||
|
|
auto [ok, values] = summaryReader->values( address );
|
||
|
|
if ( ok ) cached = std::move( values );
|
||
|
|
}
|
||
|
|
it = m_cache.emplace( address, std::move( cached ) ).first;
|
||
|
|
}
|
||
|
|
|
||
|
|
return it->second ? &it->second.value() : nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
std::optional<double> RifGroupNetworkSummaryData::valueAt( const RifEclipseSummaryAddress& address, size_t timeStepIndex ) const
|
||
|
|
{
|
||
|
|
const auto* vectorValues = values( address );
|
||
|
|
if ( !vectorValues || timeStepIndex >= vectorValues->size() ) return std::nullopt;
|
||
|
|
return ( *vectorValues )[timeStepIndex];
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
bool RifGroupNetworkSummaryData::hasPositiveValue( const RifEclipseSummaryAddress& address ) const
|
||
|
|
{
|
||
|
|
const auto* vectorValues = values( address );
|
||
|
|
if ( !vectorValues ) return false;
|
||
|
|
|
||
|
|
return std::any_of( vectorValues->begin(), vectorValues->end(), []( double value ) { return value > 0.0; } );
|
||
|
|
}
|
||
|
|
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
///
|
||
|
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
RifEclipseSummaryAddress RifGroupNetworkSummaryData::address( const std::string& vectorName, const std::string& nodeName, bool isWell )
|
||
|
|
{
|
||
|
|
if ( isWell ) return RifEclipseSummaryAddress::wellAddress( vectorName, nodeName );
|
||
|
|
if ( nodeName == fieldNodeName ) return RifEclipseSummaryAddress::fieldAddress( vectorName );
|
||
|
|
return RifEclipseSummaryAddress::groupAddress( vectorName, nodeName );
|
||
|
|
}
|