From 9229e48191a3f75f8494ed343a1e658e66a00942 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 19 Oct 2026 14:15:55 +0200 Subject: [PATCH] #14964: Use correct depth unit when creating RFT plots 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. --- .../FileInterface/RifDataSourceForRftPlt.cpp | 7 +++ .../RifMultipleSummaryReaders.cpp | 15 +++++- .../FileInterface/RifOpmCommonSummary.cpp | 54 ++++++++++++++++++- .../FileInterface/RifOpmCommonSummary.h | 8 +++ .../FileInterface/RifOpmHdf5Summary.cpp | 10 +++- .../FileInterface/RifOpmHdf5Summary.h | 5 ++ .../FileInterface/RifOpmSummaryTools.cpp | 30 +++++++++++ .../FileInterface/RifOpmSummaryTools.h | 4 ++ .../ProjectDataModel/Flow/RimWellRftPlot.cpp | 15 ++++++ .../Summary/RimSummaryEnsemble.cpp | 9 ++-- .../RifMultipleSummaryReaders-Test.cpp | 43 +++++++++++++++ .../UnitTests/opm-summary-Test.cpp | 38 +++++++++++++ 12 files changed, 230 insertions(+), 8 deletions(-) diff --git a/ApplicationLibCode/FileInterface/RifDataSourceForRftPlt.cpp b/ApplicationLibCode/FileInterface/RifDataSourceForRftPlt.cpp index 1b85bc7371..25f6d51ddd 100644 --- a/ApplicationLibCode/FileInterface/RifDataSourceForRftPlt.cpp +++ b/ApplicationLibCode/FileInterface/RifDataSourceForRftPlt.cpp @@ -146,6 +146,13 @@ std::vector RifDataSourceForRftPlt::availableUnit systems.push_back( m_eclCase->eclipseCaseData()->unitsType() ); } + auto addIfKnown = [&systems]( RiaDefines::EclipseUnitSystem system ) + { + if ( system != RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) systems.push_back( system ); + }; + if ( m_summaryCase ) addIfKnown( m_summaryCase->unitsSystem() ); + if ( m_ensemble ) addIfKnown( m_ensemble->unitSystem() ); + if ( m_wellLogFile && m_wellLogFile->wellLogData() ) { auto eclipseUnit = RiaDefines::fromDepthUnit( m_wellLogFile->wellLogData()->depthUnit() ); diff --git a/ApplicationLibCode/FileInterface/RifMultipleSummaryReaders.cpp b/ApplicationLibCode/FileInterface/RifMultipleSummaryReaders.cpp index 214563552f..bbd64f1753 100644 --- a/ApplicationLibCode/FileInterface/RifMultipleSummaryReaders.cpp +++ b/ApplicationLibCode/FileInterface/RifMultipleSummaryReaders.cpp @@ -132,7 +132,20 @@ RiaDefines::EclipseUnitSystem RifMultipleSummaryReaders::unitSystem() const { CAF_ASSERT( !m_readers.empty() ); - return m_readers.at( 0 )->unitSystem(); + // Readers without unit information are ignored, conflicting readers give an unknown unit system + auto unitSystem = RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + for ( const auto& reader : m_readers ) + { + auto readerUnitSystem = reader->unitSystem(); + if ( readerUnitSystem == RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) continue; + + if ( unitSystem == RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) + unitSystem = readerUnitSystem; + else if ( unitSystem != readerUnitSystem ) + return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + } + + return unitSystem; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifOpmCommonSummary.cpp b/ApplicationLibCode/FileInterface/RifOpmCommonSummary.cpp index 35e5cef56d..b346f26ff3 100644 --- a/ApplicationLibCode/FileInterface/RifOpmCommonSummary.cpp +++ b/ApplicationLibCode/FileInterface/RifOpmCommonSummary.cpp @@ -179,6 +179,9 @@ bool RifOpmCommonEclipseSummary::open( const QString& fileName, bool includeRest populateTimeSteps(); if ( isLoggingEnabled ) RiaLogging::logElapsedTime( "Completed populateTimeSteps", timeBeforeTimeSteps ); + m_fileName = fileName; + m_unitSystem = std::nullopt; + return true; } @@ -251,8 +254,55 @@ std::string RifOpmCommonEclipseSummary::unitName( const RifEclipseSummaryAddress //-------------------------------------------------------------------------------------------------- RiaDefines::EclipseUnitSystem RifOpmCommonEclipseSummary::unitSystem() const { - // TODO: Not implemented - return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + if ( !m_unitSystem ) m_unitSystem = detectUnitSystem(); + return *m_unitSystem; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaDefines::EclipseUnitSystem RifOpmCommonEclipseSummary::detectUnitSystem() const +{ + auto smspecFileName = RifOpmSummaryTools::smspecSummaryFilename( m_fileName ); + auto unitSystem = RifOpmSummaryTools::readUnitSystem( smspecFileName ); + + // ESMRY files can be imported without SMSPEC, and ESMRY has no unit system flag + if ( unitSystem == RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN && m_enhancedReader ) return unitSystemFromVectorUnits(); + + return unitSystem; +} + +//-------------------------------------------------------------------------------------------------- +/// Units from different systems can be mixed (PVT-M, user defined vectors), so only accept a single match +//-------------------------------------------------------------------------------------------------- +RiaDefines::EclipseUnitSystem RifOpmCommonEclipseSummary::unitSystemFromVectorUnits() const +{ + static const std::map unitToSystem = { + { "PSIA", RiaDefines::EclipseUnitSystem::UNITS_FIELD }, + { "STB", RiaDefines::EclipseUnitSystem::UNITS_FIELD }, + { "STB/DAY", RiaDefines::EclipseUnitSystem::UNITS_FIELD }, + { "MSCF/DAY", RiaDefines::EclipseUnitSystem::UNITS_FIELD }, + { "BARSA", RiaDefines::EclipseUnitSystem::UNITS_METRIC }, + { "SM3", RiaDefines::EclipseUnitSystem::UNITS_METRIC }, + { "SM3/DAY", RiaDefines::EclipseUnitSystem::UNITS_METRIC }, + { "ATMA", RiaDefines::EclipseUnitSystem::UNITS_LAB }, + { "SCC", RiaDefines::EclipseUnitSystem::UNITS_LAB }, + }; + + auto detected = RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + for ( const auto& keyword : m_enhancedReader->keywordList() ) + { + const auto unit = std::string( RiaStdStringTools::trimString( m_enhancedReader->get_unit( keyword ) ) ); + auto it = unitToSystem.find( unit ); + if ( it == unitToSystem.end() ) continue; + + if ( detected == RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) + detected = it->second; + else if ( detected != it->second ) + return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + } + + return detected; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifOpmCommonSummary.h b/ApplicationLibCode/FileInterface/RifOpmCommonSummary.h index 08167c6b38..a47a37ed28 100644 --- a/ApplicationLibCode/FileInterface/RifOpmCommonSummary.h +++ b/ApplicationLibCode/FileInterface/RifOpmCommonSummary.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -75,6 +76,9 @@ private: void populateTimeSteps(); std::string keywordForAddress( const RifEclipseSummaryAddress& address ) const; + RiaDefines::EclipseUnitSystem detectUnitSystem() const; + RiaDefines::EclipseUnitSystem unitSystemFromVectorUnits() const; + static bool writeEsmryFile( QString& smspecFileName, bool includeRestartFiles, RiaThreadSafeLogger* threadSafeLogger ); static void increaseEsmryFileCount(); @@ -90,5 +94,9 @@ private: bool m_useEsmryFiles; bool m_createEsmryFiles; + // Detected on first request, as reading the SMSPEC file is not needed for summary import + QString m_fileName; + mutable std::optional m_unitSystem; + RifEnsembleImportConfig m_ensembleImportState; }; diff --git a/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.cpp b/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.cpp index 77995837ca..f43347330c 100644 --- a/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.cpp +++ b/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.cpp @@ -98,6 +98,9 @@ bool RifOpmHdf5Summary::open( const QString& headerFileName, bool includeRestart createAndSetAddresses(); + m_headerFileName = headerFileName; + m_unitSystem = std::nullopt; + return true; } @@ -158,8 +161,11 @@ std::string RifOpmHdf5Summary::unitName( const RifEclipseSummaryAddress& resultA //-------------------------------------------------------------------------------------------------- RiaDefines::EclipseUnitSystem RifOpmHdf5Summary::unitSystem() const { - // TODO: Not implemented - return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + if ( !m_unitSystem ) + { + m_unitSystem = RifOpmSummaryTools::readUnitSystem( RifOpmSummaryTools::smspecSummaryFilename( m_headerFileName ) ); + } + return *m_unitSystem; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.h b/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.h index cd84f758e9..6c0401f1cd 100644 --- a/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.h +++ b/ApplicationLibCode/FileInterface/RifOpmHdf5Summary.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -73,4 +74,8 @@ private: std::vector m_timeSteps; std::unique_ptr m_hdf5Reader; + + // Detected on first request, as reading the SMSPEC file is not needed for summary import + QString m_headerFileName; + mutable std::optional m_unitSystem; }; diff --git a/ApplicationLibCode/FileInterface/RifOpmSummaryTools.cpp b/ApplicationLibCode/FileInterface/RifOpmSummaryTools.cpp index 5aac060a73..966415fe6d 100644 --- a/ApplicationLibCode/FileInterface/RifOpmSummaryTools.cpp +++ b/ApplicationLibCode/FileInterface/RifOpmSummaryTools.cpp @@ -23,11 +23,14 @@ #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 @@ -222,6 +225,33 @@ bool RifOpmSummaryTools::isEsmryConversionRequired( const QString& fileName ) 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( "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; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifOpmSummaryTools.h b/ApplicationLibCode/FileInterface/RifOpmSummaryTools.h index cc57f461ae..0081a4bf63 100644 --- a/ApplicationLibCode/FileInterface/RifOpmSummaryTools.h +++ b/ApplicationLibCode/FileInterface/RifOpmSummaryTools.h @@ -18,6 +18,8 @@ #pragma once +#include "RiaDefines.h" + #include "RifEclipseSummaryAddress.h" #include @@ -51,6 +53,8 @@ QString enhancedSummaryFilename( const QString& fileName ); QString smspecSummaryFilename( const QString& fileName ); bool isEsmryConversionRequired( const QString& fileName ); +RiaDefines::EclipseUnitSystem readUnitSystem( const QString& smspecFileName ); + std::expected extractRealizationNumber( const QString& path ); }; // namespace RifOpmSummaryTools diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp b/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp index dddd3a0157..76a6453b57 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp @@ -1917,6 +1917,21 @@ void RimWellRftPlot::initializeDataSources( RimWellRftPlot* source ) { // If no source is provided, initialize with empty sources setOrInitializeDataSources( {} ); + + // Use the depth unit of the data in a new plot, with the unit of the grid model in the project as fallback + auto depthUnit = RiaDefines::DepthUnitType::UNIT_NONE; + for ( const auto& dataSource : m_selectedSources() ) + { + for ( auto unitSystem : dataSource.availableUnitSystems() ) + { + depthUnit = RiaDefines::fromEclipseUnit( unitSystem ); + if ( depthUnit != RiaDefines::DepthUnitType::UNIT_NONE ) break; + } + if ( depthUnit != RiaDefines::DepthUnitType::UNIT_NONE ) break; + } + + if ( depthUnit == RiaDefines::DepthUnitType::UNIT_NONE ) depthUnit = caseDepthUnit(); + if ( depthUnit != RiaDefines::DepthUnitType::UNIT_NONE ) setDepthUnit( depthUnit ); } } diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp index af8482dd77..58f0daca75 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryEnsemble.cpp @@ -750,11 +750,14 @@ bool RimSummaryEnsemble::operator<( const RimSummaryEnsemble& rhs ) const //-------------------------------------------------------------------------------------------------- RiaDefines::EclipseUnitSystem RimSummaryEnsemble::unitSystem() const { - if ( m_cases.empty() ) + // The first realization can be missing data, use the first realization with a known unit system + for ( auto* summaryCase : m_cases.childrenByType() ) { - return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; + auto unitSystem = summaryCase->unitsSystem(); + if ( unitSystem != RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) return unitSystem; } - return m_cases[0]->unitsSystem(); + + return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/UnitTests/RifMultipleSummaryReaders-Test.cpp b/ApplicationLibCode/UnitTests/RifMultipleSummaryReaders-Test.cpp index bb21c4a068..e577d7a0f8 100644 --- a/ApplicationLibCode/UnitTests/RifMultipleSummaryReaders-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifMultipleSummaryReaders-Test.cpp @@ -31,6 +31,27 @@ public: size_t keywordCount() const override { return m_allResultAddresses.size(); } }; +//-------------------------------------------------------------------------------------------------- +/// Stand-in for a reader reporting a given unit system +//-------------------------------------------------------------------------------------------------- +class StubUnitSystemReader : public RifSummaryReaderInterface +{ +public: + explicit StubUnitSystemReader( RiaDefines::EclipseUnitSystem unitSystem ) + : m_unitSystem( unitSystem ) + { + } + + std::vector timeSteps( const RifEclipseSummaryAddress& resultAddress ) const override { return {}; } + std::pair> values( const RifEclipseSummaryAddress& resultAddress ) const override { return { false, {} }; } + std::string unitName( const RifEclipseSummaryAddress& resultAddress ) const override { return {}; } + RiaDefines::EclipseUnitSystem unitSystem() const override { return m_unitSystem; } + size_t keywordCount() const override { return 0; } + +private: + RiaDefines::EclipseUnitSystem m_unitSystem; +}; + //-------------------------------------------------------------------------------------------------- /// Create a calculation producing a single field address //-------------------------------------------------------------------------------------------------- @@ -122,3 +143,25 @@ TEST( RifMultipleSummaryReaders, RefreshCalculatedAddressesForReaderWithNoAddres RimProject::current()->calculationCollection()->deleteCalculation( calculation ); } + +//-------------------------------------------------------------------------------------------------- +/// Readers without unit information are ignored, and readers with conflicting units give an unknown unit system +//-------------------------------------------------------------------------------------------------- +TEST( RifMultipleSummaryReaders, UnitSystemFromMultipleReaders ) +{ + using Units = RiaDefines::EclipseUnitSystem; + + { + RifMultipleSummaryReaders readers; + readers.addReader( std::make_unique( Units::UNITS_UNKNOWN ) ); + readers.addReader( std::make_unique( Units::UNITS_FIELD ) ); + EXPECT_EQ( Units::UNITS_FIELD, readers.unitSystem() ); + } + + { + RifMultipleSummaryReaders readers; + readers.addReader( std::make_unique( Units::UNITS_FIELD ) ); + readers.addReader( std::make_unique( Units::UNITS_METRIC ) ); + EXPECT_EQ( Units::UNITS_UNKNOWN, readers.unitSystem() ); + } +} diff --git a/ApplicationLibCode/UnitTests/opm-summary-Test.cpp b/ApplicationLibCode/UnitTests/opm-summary-Test.cpp index 34b879e74f..9c4eeb9da9 100644 --- a/ApplicationLibCode/UnitTests/opm-summary-Test.cpp +++ b/ApplicationLibCode/UnitTests/opm-summary-Test.cpp @@ -160,6 +160,44 @@ TEST( OpmSummaryTests, ReadRegionToRegionValuesWithoutPrebuiltAddressMap ) } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( OpmSummaryTests, ReadUnitSystem ) +{ + { + RifOpmCommonEclipseSummary reader; + reader.useEnhancedSummaryFiles( false ); + reader.createEnhancedSummaryFiles( false ); + ASSERT_TRUE( reader.open( H5_TEST_DATA_DIRECTORY + "NORNE_ATW2013_RFTPLT_V2.SMSPEC", false, nullptr ) ); + EXPECT_EQ( RiaDefines::EclipseUnitSystem::UNITS_METRIC, reader.unitSystem() ); + } + + { + // ESMRY without SMSPEC, the unit system is derived from the vector units. Create the ESMRY file in a temporary folder, as the + // ESMRY file in the test data folder is rewritten by other tests. + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + const QString smspecFilePath = tempDir.filePath( "NORNE_ATW2013_RFTPLT_V2.SMSPEC" ); + const QString unsmryFilePath = tempDir.filePath( "NORNE_ATW2013_RFTPLT_V2.UNSMRY" ); + const QString esmryFilePath = tempDir.filePath( "NORNE_ATW2013_RFTPLT_V2.ESMRY" ); + ASSERT_TRUE( QFile::copy( H5_TEST_DATA_DIRECTORY + "NORNE_ATW2013_RFTPLT_V2.SMSPEC", smspecFilePath ) ); + ASSERT_TRUE( QFile::copy( H5_TEST_DATA_DIRECTORY + "NORNE_ATW2013_RFTPLT_V2.UNSMRY", unsmryFilePath ) ); + { + Opm::EclIO::ESmry eSmry( smspecFilePath.toStdString() ); + ASSERT_TRUE( eSmry.make_esmry_file() ); + } + ASSERT_TRUE( QFile::remove( smspecFilePath ) ); + ASSERT_TRUE( QFile::remove( unsmryFilePath ) ); + + RifOpmCommonEclipseSummary reader; + reader.useEnhancedSummaryFiles( true ); + reader.createEnhancedSummaryFiles( false ); + ASSERT_TRUE( reader.open( esmryFilePath, false, nullptr ) ); + EXPECT_EQ( RiaDefines::EclipseUnitSystem::UNITS_METRIC, reader.unitSystem() ); + } +} + //-------------------------------------------------------------------------------------------------- /// //--------------------------------------------------------------------------------------------------