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() ); + } +} + //-------------------------------------------------------------------------------------------------- /// //--------------------------------------------------------------------------------------------------