mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
Read the summary unit system from SMSPEC INTEHEAD, and fall back to vector units for ESMRY files without SMSPEC. The unit system is read on first request to keep summary import fast. Ensembles use the same realization as for addresses. New RFT plots get the depth unit from their data sources.
277 lines
11 KiB
C++
277 lines
11 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2025 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 "RifOpmSummaryTools.h"
|
|
|
|
#include "RiaFilePathTools.h"
|
|
#include "RiaLogging.h"
|
|
#include "RiaPreferencesSummary.h"
|
|
#include "RiaQStringFormatter.h"
|
|
|
|
#include "RifEclipseOutputFileTools.h"
|
|
|
|
#ifdef _MSC_VER
|
|
// Disable warning from external library to make sure treat warnings as error works
|
|
#pragma warning( disable : 4267 )
|
|
#endif
|
|
#include "opm/io/eclipse/ESmry.hpp"
|
|
#include "opm/io/eclipse/EclFile.hpp"
|
|
#include "opm/io/eclipse/ExtESmry.hpp"
|
|
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QRegularExpression>
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::tuple<std::set<RifEclipseSummaryAddress>, std::map<RifEclipseSummaryAddress, size_t>, std::map<RifEclipseSummaryAddress, std::string>>
|
|
RifOpmSummaryTools::buildAddressesSmspecAndKeywordMap( const Opm::EclIO::ESmry* summaryFile )
|
|
{
|
|
std::set<RifEclipseSummaryAddress> addresses;
|
|
std::map<RifEclipseSummaryAddress, size_t> addressToSmspecIndexMap;
|
|
std::map<RifEclipseSummaryAddress, std::string> addressToKeywordMap;
|
|
|
|
if ( summaryFile )
|
|
{
|
|
auto keywords = summaryFile->keywordList();
|
|
for ( const auto& keyword : keywords )
|
|
{
|
|
auto eclAdr = RifEclipseSummaryAddress::fromEclipseTextAddress( keyword );
|
|
if ( !eclAdr.isValid() )
|
|
{
|
|
// If a category is not found, use the MISC category
|
|
eclAdr = RifEclipseSummaryAddress::miscAddress( keyword );
|
|
}
|
|
|
|
if ( eclAdr.isValid() )
|
|
{
|
|
addresses.insert( eclAdr );
|
|
size_t smspecIndex = summaryFile->getSmspecIndexForKeyword( keyword );
|
|
addressToSmspecIndexMap[eclAdr] = smspecIndex;
|
|
addressToKeywordMap[eclAdr] = keyword;
|
|
}
|
|
}
|
|
}
|
|
|
|
return { addresses, addressToSmspecIndexMap, addressToKeywordMap };
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::pair<std::set<RifEclipseSummaryAddress>, std::map<RifEclipseSummaryAddress, std::string>>
|
|
RifOpmSummaryTools::buildAddressesAndKeywordMap( const std::vector<std::string>& keywords )
|
|
{
|
|
std::set<RifEclipseSummaryAddress> addresses;
|
|
std::map<RifEclipseSummaryAddress, std::string> addressToKeywordMap;
|
|
|
|
std::vector<std::string> invalidKeywords;
|
|
|
|
#pragma omp parallel
|
|
{
|
|
std::vector<RifEclipseSummaryAddress> threadAddresses;
|
|
std::vector<std::pair<RifEclipseSummaryAddress, std::string>> threadAddressToKeywordMap;
|
|
std::vector<std::string> threadInvalidKeywords;
|
|
|
|
#pragma omp for
|
|
for ( int index = 0; index < (int)keywords.size(); index++ )
|
|
{
|
|
auto keyword = keywords[index];
|
|
|
|
auto eclAdr = RifEclipseSummaryAddress::fromEclipseTextAddress( keyword );
|
|
if ( !eclAdr.isValid() )
|
|
{
|
|
threadInvalidKeywords.push_back( keyword );
|
|
|
|
// If a category is not found, use the MISC category
|
|
eclAdr = RifEclipseSummaryAddress::miscAddress( keyword );
|
|
}
|
|
|
|
if ( eclAdr.isValid() )
|
|
{
|
|
threadAddresses.emplace_back( eclAdr );
|
|
threadAddressToKeywordMap.emplace_back( std::make_pair( eclAdr, keyword ) );
|
|
}
|
|
}
|
|
|
|
#pragma omp critical( critical_section_RifOpmSummaryTools_buildAddresses )
|
|
{
|
|
addresses.insert( threadAddresses.begin(), threadAddresses.end() );
|
|
addressToKeywordMap.insert( threadAddressToKeywordMap.begin(), threadAddressToKeywordMap.end() );
|
|
invalidKeywords.insert( invalidKeywords.end(), threadInvalidKeywords.begin(), threadInvalidKeywords.end() );
|
|
}
|
|
|
|
// DEBUG code
|
|
// Used to print keywords not being categorized correctly
|
|
/*
|
|
for ( const auto& kw : invalidKeywords )
|
|
{
|
|
RiaLogging::warning( kw );
|
|
}
|
|
*/
|
|
}
|
|
|
|
return { addresses, addressToKeywordMap };
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifEclipseSummaryAddressDefines::SummaryCategory RifOpmSummaryTools::categoryFromKeyword( const std::string& keyword )
|
|
{
|
|
auto opmCategory = Opm::EclIO::SummaryNode::category_from_keyword( keyword );
|
|
switch ( opmCategory )
|
|
{
|
|
case Opm::EclIO::SummaryNode::Category::Aquifer:
|
|
return SummaryCategory::SUMMARY_AQUIFER;
|
|
case Opm::EclIO::SummaryNode::Category::Block:
|
|
return SummaryCategory::SUMMARY_BLOCK;
|
|
case Opm::EclIO::SummaryNode::Category::Connection:
|
|
return SummaryCategory::SUMMARY_WELL_CONNECTION;
|
|
case Opm::EclIO::SummaryNode::Category::Completion:
|
|
return SummaryCategory::SUMMARY_WELL_COMPLETION;
|
|
case Opm::EclIO::SummaryNode::Category::Field:
|
|
return SummaryCategory::SUMMARY_FIELD;
|
|
case Opm::EclIO::SummaryNode::Category::Group:
|
|
return SummaryCategory::SUMMARY_GROUP;
|
|
}
|
|
|
|
return SummaryCategory::SUMMARY_INVALID;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RifOpmSummaryTools::enhancedSummaryFilename( const QString& fileName )
|
|
{
|
|
QString s( fileName );
|
|
return s.replace( ".SMSPEC", ".ESMRY" );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RifOpmSummaryTools::smspecSummaryFilename( const QString& fileName )
|
|
{
|
|
QString s( fileName );
|
|
return s.replace( ".ESMRY", ".SMSPEC" );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmSummaryTools::isEsmryConversionRequired( const QString& fileName )
|
|
{
|
|
auto candidateEsmryFileName = enhancedSummaryFilename( fileName );
|
|
auto smspecFileName = smspecSummaryFilename( fileName );
|
|
|
|
// Conversion reads the SMSPEC/UNSMRY pair. Without the UNSMRY data file there is nothing to convert.
|
|
QString unsmryFileName( smspecFileName );
|
|
unsmryFileName.replace( ".SMSPEC", ".UNSMRY" );
|
|
if ( !QFile::exists( unsmryFileName ) ) return false;
|
|
|
|
if ( !QFile::exists( candidateEsmryFileName ) && QFile::exists( smspecFileName ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const int timeThresholdInSeconds = RiaPreferencesSummary::current()->esmryTimeThreshold();
|
|
if ( RiaFilePathTools::isFirstOlderThanSecond( candidateEsmryFileName.toStdString(), smspecFileName.toStdString(), timeThresholdInSeconds ) )
|
|
{
|
|
QString root = QFileInfo( smspecFileName ).canonicalPath();
|
|
|
|
const QString smspecFileNameShort = QFileInfo( smspecFileName ).fileName();
|
|
const QString esmryFileNameShort = QFileInfo( candidateEsmryFileName ).fileName();
|
|
|
|
RiaLogging::debug( std::format( " {2} : {0} is older than {1}, recreating {0}.", esmryFileNameShort, smspecFileNameShort, root ) );
|
|
|
|
// Check if we have write permission in the folder
|
|
QFileInfo info( smspecFileName );
|
|
|
|
if ( !info.isWritable() )
|
|
{
|
|
RiaLogging::error( std::format( "ESMRY is older than SMSPEC, but export to file {} failed due to missing write permissions. "
|
|
"Aborting operation.",
|
|
candidateEsmryFileName ) );
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( !std::filesystem::remove( candidateEsmryFileName.toStdString() ) )
|
|
{
|
|
RiaLogging::error( std::format( "Failed to remove file: {}", candidateEsmryFileName ) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// The unit system is stored in INTEHEAD[0] of the SMSPEC file
|
|
//--------------------------------------------------------------------------------------------------
|
|
RiaDefines::EclipseUnitSystem RifOpmSummaryTools::readUnitSystem( const QString& smspecFileName )
|
|
{
|
|
if ( !QFile::exists( smspecFileName ) ) return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN;
|
|
|
|
try
|
|
{
|
|
Opm::EclIO::EclFile smspecFile( smspecFileName.toStdString() );
|
|
if ( smspecFile.hasKey( "INTEHEAD" ) )
|
|
{
|
|
const auto& intehead = smspecFile.get<int>( "INTEHEAD" );
|
|
if ( !intehead.empty() )
|
|
{
|
|
return RifEclipseOutputFileTools::unitValueToEnum( intehead[0] ).value_or( RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN );
|
|
}
|
|
}
|
|
}
|
|
catch ( const std::exception& e )
|
|
{
|
|
RiaLogging::debug( QString( "Failed to read unit system from %1: %2" ).arg( smspecFileName, e.what() ).toStdString() );
|
|
}
|
|
|
|
return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::expected<int, QString> RifOpmSummaryTools::extractRealizationNumber( const QString& path )
|
|
{
|
|
// Regular expression to match realization number in the format "realization-<number>" or "realization_<number>"
|
|
QRegularExpression pattern( "realization[-_](\\d+)", QRegularExpression::CaseInsensitiveOption );
|
|
|
|
QRegularExpressionMatch match = pattern.match( path );
|
|
if ( match.hasMatch() )
|
|
{
|
|
bool ok;
|
|
int result = match.captured( 1 ).toInt( &ok );
|
|
if ( ok )
|
|
{
|
|
return result;
|
|
}
|
|
return std::unexpected( QString( "Invalid realization number format in path: %1" ).arg( path ) );
|
|
}
|
|
|
|
return std::unexpected( QString( "Could not extract realization number from path: %1" ).arg( path ) );
|
|
}
|