Files
ResInsight/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp
T

594 lines
24 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2016- Statoil 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 "RimFileSummaryCase.h"
#include "RiaApplication.h"
#include "RiaLogging.h"
#include "RiaPreferencesSummary.h"
#include "RifEclipseSummaryAddress.h"
#include "RifEclipseSummaryTools.h"
#include "RifMultipleSummaryReaders.h"
#include "RifOpmCommonSummary.h"
#include "RifOpmSummaryTools.h"
#include "RifProjectSummaryDataWriter.h"
#include "RifReaderEclipseSummary.h"
#include "RifReaderOpmRft.h"
#include "RifSummaryReaderAggregator.h"
#include "RimCalculatedSummaryCurveReader.h"
#include "RimProject.h"
#include "RimRftCase.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiFilePathEditor.h"
#include "cafPdmUiTreeOrdering.h"
#include "RiaPreferencesSystem.h"
#include <QDir>
#include <QFileInfo>
#include <QUuid>
//==================================================================================================
//
//
//
//==================================================================================================
CAF_PDM_SOURCE_INIT( RimFileSummaryCase, "FileSummaryCase" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimFileSummaryCase::RimFileSummaryCase()
{
CAF_PDM_InitScriptableObject( "File Summary Case ", ":/SummaryCases16x16.png", "", "A Summary Case based on SMSPEC files" );
CAF_PDM_InitScriptableField( &m_includeRestartFiles, "IncludeRestartFiles", false, "Include Restart Files" );
m_includeRestartFiles.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_additionalSummaryFilePath, "AdditionalSummaryFilePath", "Additional File Path (set invisible when ready)" );
m_additionalSummaryFilePath.uiCapability()->setUiHidden( true );
m_additionalSummaryFilePath.uiCapability()->setAttribute( caf::PdmUiFilePathEditor::Keys::SELECT_SAVE_FILE_NAME, true );
CAF_PDM_InitFieldNoDefault( &m_rftCase, "RftCase", "RFT Data" );
m_rftCase = new RimRftCase;
m_hasParsedForWeseglink = false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimFileSummaryCase::summaryHeaderFilename() const
{
return m_summaryHeaderFilename().path();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimFileSummaryCase::caseName() const
{
QFileInfo caseFileName( summaryHeaderFilename() );
return caseFileName.completeBaseName();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::createSummaryReaderInterfaceThreadSafe( RifEnsembleImportConfig ensembleImportState,
RiaThreadSafeLogger* threadSafeLogger )
{
// RimFileSummaryCase::findRelatedFilesAndCreateReader is a performance bottleneck. The function
// RifEclipseSummaryTools::getRestartFile() should be refactored to use opm-common instead of resdata.
// It is not possible to use restart files in ESMRY file format, see see ESmry::make_esmry_file()
//
// https://github.com/OPM/ResInsight/issues/11342
auto multiReader = std::make_unique<RifMultipleSummaryReaders>();
m_fileSummaryReaderId = -1;
m_additionalSummaryReaderId = -1;
auto reader = RimFileSummaryCase::findRelatedFilesAndCreateReader( summaryHeaderFilename(),
m_includeRestartFiles,
ensembleImportState,
threadSafeLogger );
if ( !reader ) return;
m_fileSummaryReaderId = reader->serialNumber();
m_multiSummaryReader = std::move( multiReader );
m_multiSummaryReader->addReader( std::move( reader ) );
openAndAttachAdditionalReader();
m_multiSummaryReader->addReader( std::make_unique<RifCalculatedSummaryCurveReader>( this ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::createSummaryReaderInterface()
{
RiaThreadSafeLogger threadSafeLogger;
createSummaryReaderInterfaceThreadSafe( RifEnsembleImportConfig(), &threadSafeLogger );
auto messages = threadSafeLogger.messages();
for ( const auto& m : messages )
{
RiaLogging::info( m.toStdString() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::createRftReaderInterface()
{
QFileInfo fileInfo( summaryHeaderFilename() );
QString folder = fileInfo.absolutePath();
QString rftFileName = folder + "/" + fileInfo.completeBaseName() + ".RFT";
{
QFileInfo fi( rftFileName );
if ( fi.exists() )
{
m_rftCase()->setRftFileName( rftFileName );
}
}
// Usually, the data deck file path is empty at this point. If the user has specified the path manually, we use that.
// The intention is to use searchForWseglinkAndRecreateRftReader() to search for the data deck file path. Here we avoid searching for
// the data deck file by default, as this is a time consuming operation.
m_summaryEclipseRftReader = RimFileSummaryCase::createOpmRftReader( rftFileName, m_rftCase->dataDeckFilePath() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::unique_ptr<RifSummaryReaderInterface> RimFileSummaryCase::findRelatedFilesAndCreateReader( const QString& headerFileName,
bool lookForRestartFiles,
RifEnsembleImportConfig ensembleImportState,
RiaThreadSafeLogger* threadSafeLogger )
{
if ( lookForRestartFiles )
{
auto startTime = RiaLogging::currentTime();
std::vector<QString> warnings;
auto findFileCandidates = [&ensembleImportState, &headerFileName, &warnings] -> std::vector<QString>
{
if ( ( RiaPreferencesSummary::current()->summaryDataReader() == RiaPreferencesSummary::SummaryReaderMode::OPM_COMMON ) &&
ensembleImportState.useConfigValues() )
{
auto realizationNumber = RifOpmSummaryTools::extractRealizationNumber( headerFileName );
if ( !realizationNumber.has_value() )
{
RiaLogging::error( realizationNumber.error().toStdString() );
return {};
}
return ensembleImportState.restartFilesForRealization( realizationNumber.value() );
}
return RifEclipseSummaryTools::getRestartFileNames( headerFileName, warnings );
};
std::vector<QString> restartFileNames;
for ( const auto& fileName : findFileCandidates() )
{
QFileInfo fi( fileName );
if ( fi.exists() )
{
restartFileNames.push_back( fileName );
}
}
bool isLoggingEnabled = RiaPreferencesSystem::current()->isLoggingActivatedForKeyword( "OpmSummaryImport" );
if ( isLoggingEnabled ) RiaLogging::logElapsedTime( "Searched for restart files for " + headerFileName.toStdString(), startTime );
if ( !restartFileNames.empty() )
{
auto startTime = RiaLogging::currentTime();
std::vector<std::string> summaryFileNames;
summaryFileNames.push_back( headerFileName.toStdString() );
for ( const auto& fileName : restartFileNames )
{
summaryFileNames.push_back( fileName.toStdString() );
}
// The ordering in intended to be start of history first, so we reverse the ordering
std::reverse( summaryFileNames.begin(), summaryFileNames.end() );
auto summaryReader = std::make_unique<RifSummaryReaderAggregator>( summaryFileNames );
if ( !summaryReader->createReadersAndImportMetaData( ensembleImportState, threadSafeLogger ) )
{
return nullptr;
}
bool isLoggingEnabled = RiaPreferencesSystem::current()->isLoggingActivatedForKeyword( "OpmSummaryImport" );
if ( isLoggingEnabled ) RiaLogging::logElapsedTime( "Created reader", startTime );
return summaryReader;
}
}
auto summaryFileReader = std::make_unique<RifReaderEclipseSummary>();
summaryFileReader->setEnsembleImportState( ensembleImportState );
// All restart data is taken care of by RifSummaryReaderAggregator, never read restart data from native file
// readers
if ( !summaryFileReader->open( headerFileName, threadSafeLogger ) )
{
return nullptr;
}
return summaryFileReader;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::unique_ptr<RifReaderOpmRft> RimFileSummaryCase::createOpmRftReader( const QString& rftFileName, const QString& dataDeckFileName )
{
QFileInfo fi( rftFileName );
if ( fi.exists() )
{
return std::make_unique<RifReaderOpmRft>( rftFileName, dataDeckFileName );
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName /*= "" */ )
{
RimSummaryCase::defineUiTreeOrdering( uiTreeOrdering, uiConfigName );
if ( rftReader() )
{
uiTreeOrdering.add( m_rftCase() );
}
uiTreeOrdering.skipRemainingChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::openAndAttachAdditionalReader()
{
QString additionalSummaryFilePath = m_additionalSummaryFilePath().path();
if ( additionalSummaryFilePath.isEmpty() ) return;
auto opmCommonReader = std::make_unique<RifOpmCommonEclipseSummary>();
opmCommonReader->useEnhancedSummaryFiles( true );
bool includeRestartFiles = false;
auto isValid = opmCommonReader->open( additionalSummaryFilePath, includeRestartFiles, nullptr );
if ( isValid )
{
// The additional file is small, and the addresses are required to read values also when the addresses of the case are not
// created (e.g. realizations in an ensemble)
opmCommonReader->createAddressesIfRequired();
m_additionalSummaryReaderId = opmCommonReader->serialNumber();
m_multiSummaryReader->addReader( std::move( opmCommonReader ) );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RifSummaryReaderInterface* RimFileSummaryCase::summaryReader()
{
if ( !m_multiSummaryReader )
{
createSummaryReaderInterface();
}
return m_multiSummaryReader.get();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RifReaderRftInterface* RimFileSummaryCase::rftReader()
{
if ( !m_summaryEclipseRftReader )
{
createRftReaderInterface();
}
return m_summaryEclipseRftReader.get();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::setIncludeRestartFiles( bool includeRestartFiles )
{
m_includeRestartFiles = includeRestartFiles;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<void, QString>
RimFileSummaryCase::setSummaryData( const std::string& keyword, const std::string& unit, const std::vector<float>& values )
{
// Validate keyword length (Eclipse SMSPEC format limitation)
constexpr size_t MAX_KEYWORD_LENGTH = 8;
if ( keyword.length() > MAX_KEYWORD_LENGTH )
{
QString errorMsg = QString( "Keyword '%1' exceeds the maximum length of %2 characters (SMSPEC format limitation). "
"Keyword has %3 characters. Please use a shorter keyword name." )
.arg( QString::fromStdString( keyword ) )
.arg( MAX_KEYWORD_LENGTH )
.arg( keyword.length() );
RiaLogging::error( errorMsg.toStdString() );
return std::unexpected( errorMsg );
}
auto address = RifEclipseSummaryAddress::fromEclipseTextAddressParseErrorTokens( keyword );
auto fileReader = m_multiSummaryReader->findReader( m_fileSummaryReaderId );
if ( fileReader )
{
// Addresses are created lazily, and can be missing for realizations in an ensemble
fileReader->createAddressesIfRequired();
}
if ( fileReader && fileReader->hasAddress( address ) )
{
QString errorMsg = QString( "Cannot set summary values for '%1' because the vector comes from the source summary file. "
"Use a new summary vector address instead." )
.arg( QString::fromStdString( keyword ) );
RiaLogging::error( errorMsg.toStdString() );
return std::unexpected( errorMsg );
}
// Validate value count matches main summary file
size_t mainSummaryFileValueCount = m_multiSummaryReader->timeSteps( RifEclipseSummaryAddress() ).size();
if ( values.size() != mainSummaryFileValueCount )
{
QString errorMsg = QString( "Wrong size of summary data for keyword %1. Expected %2 values, received %3 values" )
.arg( QString::fromStdString( keyword ) )
.arg( mainSummaryFileValueCount )
.arg( values.size() );
RiaLogging::error( errorMsg.toStdString() );
return std::unexpected( errorMsg );
}
m_multiSummaryReader->removeReader( m_additionalSummaryReaderId );
RifProjectSummaryDataWriter projectSummaryDataWriter;
QString tmpAdditionalSummaryFilePath = m_additionalSummaryFilePath().path();
QFileInfo fi( tmpAdditionalSummaryFilePath );
if ( fi.exists() )
{
projectSummaryDataWriter.importFromProjectSummaryFile( tmpAdditionalSummaryFilePath.toStdString() );
}
else
{
auto fileReader = m_multiSummaryReader->findReader( m_fileSummaryReaderId );
if ( fileReader )
{
projectSummaryDataWriter.importFromSourceSummaryReader( fileReader );
auto tempFilePath = additionalSummaryDataFilePath();
m_additionalSummaryFilePath = tempFilePath;
tmpAdditionalSummaryFilePath = tempFilePath;
}
}
projectSummaryDataWriter.setData( { keyword }, { unit }, { values } );
std::string outputFilePath = tmpAdditionalSummaryFilePath.toStdString();
projectSummaryDataWriter.writeDataToFile( outputFilePath );
// Check for errors during write operation
auto errorMessages = projectSummaryDataWriter.errorMessages();
if ( !errorMessages.empty() )
{
QString combinedErrors;
for ( const auto& txt : errorMessages )
{
RiaLogging::error( txt );
if ( !combinedErrors.isEmpty() ) combinedErrors += "; ";
combinedErrors += QString::fromStdString( txt );
}
projectSummaryDataWriter.clearErrorMessages();
return std::unexpected( combinedErrors );
}
projectSummaryDataWriter.clearErrorMessages();
openAndAttachAdditionalReader();
m_multiSummaryReader->createAndSetAddresses();
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimFileSummaryCase::additionalSummaryFilePath() const
{
return m_additionalSummaryFilePath().path();
}
//--------------------------------------------------------------------------------------------------
/// Attach an existing file with user-defined summary vectors. Used when the case is recreated, e.g. when
/// an ensemble is defined by a file set and the cases are not stored in the project file.
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::setAdditionalSummaryFilePath( const QString& filePath )
{
if ( filePath.isEmpty() || !QFile::exists( filePath ) ) return;
m_additionalSummaryFilePath = filePath;
if ( m_multiSummaryReader )
{
m_multiSummaryReader->removeReader( m_additionalSummaryReaderId );
openAndAttachAdditionalReader();
// Addresses are created lazily for realizations in an ensemble, only refresh if already created
if ( !m_multiSummaryReader->allResultAddresses().empty() )
{
m_multiSummaryReader->createAndSetAddresses();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::onProjectBeingSaved()
{
// If additional data is stored in temp folder, copy to project folder and remove file in temp folder. Files in other folders are
// copied and kept, so a project saved with Save As does not lose the data of the original project. The project file is not written
// yet, so additionalSummaryDataFilePath() would still point to the temp folder on the first save.
const QString projectFileName = RimProject::current() ? RimProject::current()->fileName() : QString();
if ( projectFileName.isEmpty() ) return;
auto existingFilePath = m_additionalSummaryFilePath().path();
if ( QFile::exists( existingFilePath ) )
{
auto currentFilePath = QFileInfo( projectFileName ).dir().absoluteFilePath( QFileInfo( existingFilePath ).fileName() );
if ( existingFilePath != currentFilePath )
{
if ( m_multiSummaryReader ) m_multiSummaryReader->removeReader( m_additionalSummaryReaderId );
if ( QFile::copy( existingFilePath, currentFilePath ) )
{
const bool isInTempFolder = QFileInfo( existingFilePath ).absoluteDir() == QDir( QDir::tempPath() );
if ( isInTempFolder ) QFile::remove( existingFilePath );
m_additionalSummaryFilePath = currentFilePath;
}
else
{
QString txt = "Error when copying temporary file to " + currentFilePath;
RiaLogging::error( txt.toStdString() );
}
if ( m_multiSummaryReader ) openAndAttachAdditionalReader();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimFileSummaryCase::createAdditionalSummaryFileName()
{
QUuid uuid = QUuid::createUuid();
QString uuidString = uuid.toString();
uuidString.remove( '{' ).remove( '}' );
auto filePath = "RI_SUMMARY_DATA_" + uuidString + ".ESMRY";
return filePath;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimFileSummaryCase::searchForWseglinkAndRecreateRftReader()
{
if ( m_hasParsedForWeseglink ) return;
if ( m_rftCase->dataDeckFilePath().isEmpty() )
{
QFileInfo fileInfo( summaryHeaderFilename() );
QString folder = fileInfo.absolutePath();
// Search for *.DATA file in same folder as summary file. If not found, search for a schedule file.
QString validDataDeckFileName;
QString dataDeckFileName = folder + "/" + fileInfo.completeBaseName() + ".DATA";
QFileInfo fi( dataDeckFileName );
if ( fi.exists() )
{
validDataDeckFileName = dataDeckFileName;
}
else
{
QString scheduleFileName = folder + "/" + fileInfo.completeBaseName() + ".SCH";
QFileInfo fi( scheduleFileName );
if ( fi.exists() )
{
validDataDeckFileName = scheduleFileName;
}
}
if ( !validDataDeckFileName.isEmpty() )
{
m_rftCase->setDataDeckFileName( dataDeckFileName );
// Create a new reader including the file path to the data deck file
// NB! This can be a time consuming operation
m_summaryEclipseRftReader = RimFileSummaryCase::createOpmRftReader( m_rftCase->rftFilePath(), m_rftCase->dataDeckFilePath() );
}
}
m_hasParsedForWeseglink = true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimFileSummaryCase::additionalSummaryDataFilePath() const
{
QDir storageDir;
RiaApplication* app = RiaApplication::instance();
if ( app->isProjectSavedToDisc() )
{
QString projectFileName = RimProject::current()->fileName();
QFileInfo fileInfo( projectFileName );
storageDir = fileInfo.dir();
}
else
{
// If project is not saved, use the temp folder
storageDir = QDir::temp();
}
QString fileName;
auto cacheSummaryFilePath = m_additionalSummaryFilePath().path();
if ( cacheSummaryFilePath.isEmpty() )
{
fileName = createAdditionalSummaryFileName();
}
else
{
QFileInfo fi( cacheSummaryFilePath );
fileName = fi.fileName();
}
auto filePath = storageDir.absoluteFilePath( fileName );
return filePath;
}