///////////////////////////////////////////////////////////////////////////////// // // 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 // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RimSummaryCaseMainCollection.h" #include "RiaEclipseFileNameTools.h" #include "RiaEnsembleNameTools.h" #include "RiaLogging.h" #include "RiaOpenMPTools.h" #include "RiaPreferencesSummary.h" #include "RiaPreferencesSystem.h" #include "RiaQStringFormatter.h" #include "Summary/RiaSummaryTools.h" #include "Cloud/RimSummaryCaseSumo.h" #include "RifCaseRealizationParametersReader.h" #include "RifEclipseSummaryTools.h" #include "RifOpmCommonSummary.h" #include "RifOpmSummaryTools.h" #include "RifSummaryCaseRestartSelector.h" #ifdef USE_HDF5 #include "RifHdf5SummaryExporter.h" #endif #include "Ensemble/RimSummaryFileSetEnsemble.h" #include "RimCaseDisplayNameTools.h" #include "RimCsvSummaryCase.h" #include "RimDeltaSummaryEnsemble.h" #include "RimEclipseResultCase.h" #include "RimFileSummaryCase.h" #include "RimMainPlotCollection.h" #include "RimOilField.h" #include "RimProject.h" #include "RimRftPlotCollection.h" #include "RimSummaryCase.h" #include "RimSummaryCaseUpdateBatch.h" #include "RimSummaryCurve.h" #include "RimSummaryEnsemble.h" #include "RimSummaryMultiPlotCollection.h" #include "RimSummaryPlot.h" #include "RimWellRftPlot.h" #include "cafCmdFeatureMenuBuilder.h" #include "cafPdmFieldReorderCapability.h" #include "cafPdmPointer.h" #include "cafProgressInfo.h" #include #include #include #include #include CAF_PDM_SOURCE_INIT( RimSummaryCaseMainCollection, "SummaryCaseCollection" ); //-------------------------------------------------------------------------------------------------- /// Internal function. An empty rmsSeedFileCandidate means the RMS seed file is known to be absent, while std::nullopt /// means the file system must be searched for the file. //-------------------------------------------------------------------------------------------------- void addCaseRealizationParametersIfFound( RimSummaryCase& sumCase, const QString modelFolderOrFile, const QString& filePathCandidate, const std::optional& rmsSeedFileCandidate ) { std::shared_ptr parameters; QString parametersFile = filePathCandidate.isEmpty() ? RifCaseRealizationParametersFileLocator::locate( modelFolderOrFile ) : filePathCandidate; if ( !parametersFile.isEmpty() ) { auto reader = RifCaseRealizationReader::createReaderFromFileName( parametersFile ); if ( reader ) { // Try parse case realization parameters try { reader->parse(); parameters = reader->parameters(); } catch ( ... ) { } } } if ( !parameters ) { parameters = std::make_shared(); } if ( dynamic_cast( &sumCase ) == nullptr ) { int realizationNumber = RifCaseRealizationParametersFileLocator::realizationNumber( modelFolderOrFile ); parameters->setRealizationNumber( realizationNumber ); parameters->addParameter( RiaDefines::summaryRealizationNumber(), realizationNumber ); // If present, expose the RMS seed value from "rms/model/RMS_SEED_USED" as a realization parameter. Ignore missing // or malformed files silently. QString rmsSeedFile = rmsSeedFileCandidate.has_value() ? rmsSeedFileCandidate.value() : RifRmsSeedFileReader::locate( modelFolderOrFile ); if ( !rmsSeedFile.isEmpty() ) { if ( auto seedValue = RifRmsSeedFileReader::readSeedValue( rmsSeedFile ) ) { parameters->addParameter( RiaDefines::summaryRmsSeed(), seedValue.value() ); } } sumCase.setCaseRealizationParameters( parameters ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryCaseMainCollection::RimSummaryCaseMainCollection() : dataSourceHasChanged( this ) { CAF_PDM_InitObject( "Summary Cases", ":/SummaryCases16x16.png" ); CAF_PDM_InitFieldNoDefault( &m_cases, "SummaryCases", "" ); caf::PdmFieldReorderCapability::addToField( &m_cases ); CAF_PDM_InitFieldNoDefault( &m_ensembles, "SummaryCaseCollections", "" ); caf::PdmFieldReorderCapability::addToField( &m_ensembles ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryCaseMainCollection::~RimSummaryCaseMainCollection() { m_cases.deleteChildren(); m_ensembles.deleteChildren(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryCase* RimSummaryCaseMainCollection::findTopLevelSummaryCaseFromFileName( const QString& fileName ) const { for ( const auto& summaryCase : topLevelSummaryCases() ) { if ( summaryCase->summaryHeaderFilename() == fileName ) { return summaryCase; } } return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::addCases( const std::vector cases ) { for ( RimSummaryCase* sumCase : cases ) { m_cases.push_back( sumCase ); sumCase->nameChanged.connect( this, &RimSummaryCaseMainCollection::onCaseNameChanged ); } dataSourceHasChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::addCase( RimSummaryCase* summaryCase ) { m_cases.push_back( summaryCase ); summaryCase->nameChanged.connect( this, &RimSummaryCaseMainCollection::onCaseNameChanged ); dataSourceHasChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::removeCase( RimSummaryCase* summaryCase, bool notifyChange ) { std::vector derivedEnsembles; // Build a list of derived ensembles that must be updated after delete for ( auto ensemble : summaryEnsembles() ) { auto derEnsemble = dynamic_cast( ensemble ); if ( derEnsemble ) { if ( derEnsemble->hasCaseReference( summaryCase ) ) { derivedEnsembles.push_back( derEnsemble ); } } } m_cases.removeChild( summaryCase ); for ( RimSummaryEnsemble* ensemble : m_ensembles ) { ensemble->removeCase( summaryCase, notifyChange ); } // Update derived ensemble cases (if any) for ( auto derEnsemble : derivedEnsembles ) { derEnsemble->createDerivedEnsembleCases(); } if ( notifyChange ) dataSourceHasChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::removeCases( const std::vector& cases ) { // Removing one case makes a delta ensemble rebuild its derived cases, and some of those derived cases can be part // of this very list. The batch detaches them now and destroys them when the outermost batch scope ends, so a caller // holding on to the list across this call never sees a freed case, as long as it opens a batch of its own. RimSummaryCaseUpdateBatch updateBatch; for ( auto sumCase : cases ) { removeCase( sumCase, false ); } for ( RimSummaryEnsemble* ensemble : m_ensembles ) { ensemble->updateReferringCurveSetsZoomAll(); } dataSourceHasChanged.send(); } //-------------------------------------------------------------------------------------------------- /// Lambda function to move an object in a child array field //-------------------------------------------------------------------------------------------------- auto moveObjectInChildArrayField = []( auto& childArrayField, auto* object, int destinationIndex ) { auto currentIndex = childArrayField.indexOf( object ); if ( currentIndex < childArrayField.size() ) { childArrayField.erase( currentIndex ); childArrayField.insertAt( std::min( destinationIndex, static_cast( childArrayField.size() ) ), object ); } }; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::moveCase( RimSummaryCase* summaryCase, int destinationIndex ) { moveObjectInChildArrayField( m_cases, summaryCase, destinationIndex ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryEnsemble* RimSummaryCaseMainCollection::addEnsemble( const std::vector& summaryCases, const QString& collectionName, bool isEnsemble, std::function allocator ) { RimSummaryEnsemble* ensemble = allocator(); if ( !collectionName.isEmpty() ) ensemble->setNameTemplate( collectionName ); for ( RimSummaryCase* summaryCase : summaryCases ) { auto currentSummaryCaseCollection = summaryCase->firstAncestorOrThisOfType(); if ( currentSummaryCaseCollection ) { currentSummaryCaseCollection->removeCase( summaryCase ); } else { m_cases.removeChild( summaryCase ); } ensemble->addCase( summaryCase ); if ( isEnsemble ) { summaryCase->setDisplayNameOption( RimCaseDisplayNameTools::DisplayName::SHORT_CASE_NAME ); } } ensemble->setAsEnsemble( isEnsemble ); addEnsemble( ensemble ); return ensemble; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::moveEnsemble( RimSummaryEnsemble* ensemble, int destinationIndex ) { moveObjectInChildArrayField( m_ensembles, ensemble, destinationIndex ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::removeEnsemble( RimSummaryEnsemble* ensemble ) { m_ensembles.removeChild( ensemble ); RiaSummaryTools::updateSummaryEnsembleNames(); dataSourceHasChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::addEnsemble( RimSummaryEnsemble* ensemble ) { CAF_ASSERT( ensemble ); m_ensembles.push_back( ensemble ); if ( ensemble->ensembleId() == -1 ) { RimProject* project = RimProject::current(); project->assignIdToEnsemble( ensemble ); } ensemble->caseNameChanged.connect( this, &RimSummaryCaseMainCollection::onCaseNameChanged ); dataSourceHasChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryCase* RimSummaryCaseMainCollection::summaryCase( size_t idx ) { return m_cases[idx]; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- size_t RimSummaryCaseMainCollection::summaryCaseCount() const { return m_cases.size(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryCaseMainCollection::allSummaryCases() const { std::vector cases; if ( !m_cases.empty() ) cases.insert( cases.end(), m_cases.begin(), m_cases.end() ); for ( auto& ensemble : m_ensembles ) { auto collCases = ensemble->allSummaryCases(); if ( collCases.empty() ) continue; cases.insert( cases.end(), collCases.begin(), collCases.end() ); } return cases; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryCaseMainCollection::topLevelSummaryCases() const { std::vector cases; for ( const auto& sumCase : m_cases ) { cases.push_back( sumCase ); } return cases; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryCaseMainCollection::summaryEnsembles() const { std::vector ensembles; for ( const auto& ensemble : m_ensembles ) { ensembles.push_back( ensemble ); } return ensembles; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryCaseMainCollection::summaryFileSetEnsembles() const { std::vector ensembles; for ( auto ensemble : m_ensembles.childrenByType() ) { if ( auto fileSetEnsemble = dynamic_cast( ensemble ) ) { ensembles.push_back( fileSetEnsemble ); } } return ensembles; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::loadAllSummaryCaseData() { for ( auto ensemble : summaryEnsembles() ) { auto sumCases = ensemble->allSummaryCases(); const bool extractStateFromFirstCase = true; RimSummaryCaseMainCollection::loadSummaryCaseData( sumCases, extractStateFromFirstCase ); } std::vector sumCases = topLevelSummaryCases(); const bool extractStateFromFirstCase = false; RimSummaryCaseMainCollection::loadSummaryCaseData( sumCases, extractStateFromFirstCase ); // Create addresses for all single summary cases (not part of an ensemble) for ( auto sumCase : sumCases ) { if ( sumCase->summaryReader() ) { sumCase->summaryReader()->createAddressesIfRequired(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicImportSummaryCaseFeature"; menuBuilder << "RicImportGridAndSummaryEnsembleFeature"; menuBuilder.subMenuStart( "More", QIcon( ":/import.svg" ) ); menuBuilder << "RicImportRevealSummaryCaseFeature"; menuBuilder << "RicImportStimPlanSummaryCaseFeature"; menuBuilder << "RicImportSummaryCasesFeature"; menuBuilder << "RicImportSummaryGroupFeature"; menuBuilder << "RicImportEnsembleFeature"; menuBuilder.subMenuEnd(); menuBuilder << "Separator"; menuBuilder << "RicNewDerivedEnsembleFeature"; menuBuilder << "RicNewDerivedSummaryFeature"; menuBuilder << "Separator"; menuBuilder << "RicShowSummaryCurveCalculatorFeature"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::initAfterRead() { for ( auto sumCase : topLevelSummaryCases() ) { sumCase->nameChanged.connect( this, &RimSummaryCaseMainCollection::onCaseNameChanged ); } for ( auto ensemble : summaryEnsembles() ) { ensemble->caseNameChanged.connect( this, &RimSummaryCaseMainCollection::onCaseNameChanged ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::loadSummaryCaseData( const std::vector& summaryCases, bool extractStateFromFirstCase ) { std::vector fileSummaryCases; std::vector otherSummaryCases; RiaPreferencesSummary* prefs = RiaPreferencesSummary::current(); if ( prefs->useMultipleThreadsWhenLoadingSummaryData() ) { for ( auto c : summaryCases ) { auto fileCase = dynamic_cast( c ); if ( fileCase ) { fileSummaryCases.push_back( fileCase ); } else { otherSummaryCases.push_back( c ); } } } else { otherSummaryCases = summaryCases; } if ( !fileSummaryCases.empty() ) { loadFileSummaryCaseData( fileSummaryCases, extractStateFromFirstCase ); } if ( !otherSummaryCases.empty() ) { caf::ProgressInfo progInfo( otherSummaryCases.size(), "Loading Summary Cases" ); for ( int cIdx = 0; cIdx < static_cast( otherSummaryCases.size() ); ++cIdx ) { RimSummaryCase* sumCase = otherSummaryCases[cIdx]; if ( sumCase ) { sumCase->createSummaryReaderInterface(); sumCase->createRftReaderInterface(); addCaseRealizationParametersIfFound( *sumCase, sumCase->summaryHeaderFilename(), {}, std::nullopt ); } { progInfo.incrementProgress(); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::loadFileSummaryCaseData( const std::vector& fileSummaryCases, bool extractStateFromFirstCase ) { RiaPreferencesSummary* prefs = RiaPreferencesSummary::current(); // If h5 mode, check all summary files and create or recreate h5 files if required #ifdef USE_HDF5 { if ( prefs->summaryDataReader() == RiaPreferencesSummary::SummaryReaderMode::HDF5_OPM_COMMON ) { int threadCount = 1; #ifdef USE_OPENMP threadCount = prefs->createH5SummaryDataThreadCount(); #endif std::vector headerFileNames; std::vector h5FileNames; for ( const auto fileSummaryCase : fileSummaryCases ) { auto headerFileName = fileSummaryCase->summaryHeaderFilename(); QFileInfo fi( headerFileName ); // NB! Use canonicalPath to make sure any symlinks are resolved to absolute file paths QString h5FilenameCandidate = fi.canonicalPath() + "/" + fi.completeBaseName() + ".h5"; headerFileNames.push_back( headerFileName.toStdString() ); h5FileNames.push_back( h5FilenameCandidate.toStdString() ); } RifHdf5SummaryExporter::ensureHdf5FileIsCreatedMultithreaded( headerFileNames, h5FileNames, prefs->createH5SummaryDataFiles(), threadCount ); } } #endif RifEnsembleImportConfig importState; if ( !RiaPreferencesSummary::current()->useImprovedSummaryImport() ) { extractStateFromFirstCase = false; } if ( extractStateFromFirstCase ) { // If we are extracting state from the first case, we need to make sure that the first case is loaded // before we start loading the rest of the cases. if ( fileSummaryCases.size() > 1 ) { std::vector warnings; auto headerFileName0 = fileSummaryCases[0]->summaryHeaderFilename(); auto headerFileName1 = fileSummaryCases[1]->summaryHeaderFilename(); RifEnsembleImportConfig state; state.computePatternsFromSummaryFilePaths( headerFileName0, headerFileName1 ); importState = state; } } // Use openMP when reading file summary case meta data. Avoid using the virtual interface of base class // RimSummaryCase, as it is difficult to make sure all variants of the leaf classes are thread safe. // Only open the summary file reader in parallel loop to reduce risk of multi threading issues { caf::ProgressInfo progInfo( fileSummaryCases.size(), "Loading Summary Cases" ); RifOpmCommonEclipseSummary::resetEnhancedSummaryFileCount(); RiaThreadSafeLogger threadSafeLogger; QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); // The HDF5 reader requires a special configuration to be thread safe. Disable threading for HDF reader. [[maybe_unused]] bool canUseMultipleThreads = ( prefs->summaryDataReader() != RiaPreferencesSummary::SummaryReaderMode::HDF5_OPM_COMMON ); std::atomic completedCount = 0; #pragma omp parallel for schedule( dynamic ) if ( canUseMultipleThreads ) for ( int cIdx = 0; cIdx < static_cast( fileSummaryCases.size() ); ++cIdx ) { RimFileSummaryCase* fileSummaryCase = fileSummaryCases[cIdx]; if ( fileSummaryCase ) { fileSummaryCase->createSummaryReaderInterfaceThreadSafe( importState, &threadSafeLogger ); QString parameterFilePath; std::optional rmsSeedFilePath; if ( importState.useConfigValues() ) { auto realizationNumber = RifOpmSummaryTools::extractRealizationNumber( fileSummaryCase->summaryHeaderFilename() ); if ( realizationNumber.has_value() ) { parameterFilePath = importState.pathToParameterFile( realizationNumber.value() ); rmsSeedFilePath = importState.pathToRmsSeedFile( realizationNumber.value() ); } } auto startTime = RiaLogging::currentTime(); addCaseRealizationParametersIfFound( *fileSummaryCase, fileSummaryCase->summaryHeaderFilename(), parameterFilePath, rmsSeedFilePath ); bool isLoggingEnabled = RiaPreferencesSystem::current()->isLoggingActivatedForKeyword( "OpmSummaryImport" ); if ( isLoggingEnabled ) { RiaLogging::logElapsedTime( "Setting of realization parameters", startTime ); RiaLogging::info( std::format( "Completed {}", fileSummaryCase->summaryHeaderFilename() ) ); } } int completed = ++completedCount; // Only update progress from the GUI thread; worker-thread updates are marshaled via a // queued connection and may be delivered after this ProgressInfo scope has ended. if ( RiaOpenMPTools::currentThreadIndex() == 0 ) { progInfo.setProgress( completed ); } } for ( const auto& txt : threadSafeLogger.messages() ) { RiaLogging::info( txt.toStdString() ); } } auto numberOfEsmryFilesCreated = RifOpmCommonEclipseSummary::numberOfEnhancedSummaryFileCreated(); if ( numberOfEsmryFilesCreated > 0 ) { RiaLogging::info( std::format( "Summary Files : Converted and created {} '*.ESMRY' files on disk.", numberOfEsmryFilesCreated ) ); } // This loop is not thread safe, use serial loop for ( RimFileSummaryCase* fileSummaryCase : fileSummaryCases ) { if ( fileSummaryCase ) { fileSummaryCase->createRftReaderInterface(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryEnsemble* RimSummaryCaseMainCollection::defaultAllocator() { return new RimSummaryEnsemble(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::onCaseNameChanged( const SignalEmitter* emitter ) { RiaSummaryTools::updateSummaryEnsembleNames(); RimSummaryMultiPlotCollection* summaryPlotColl = RiaSummaryTools::summaryMultiPlotCollection(); summaryPlotColl->updateSummaryNameHasChanged(); // Update RFT plots, as they may depend on the summary case ensemble names auto rftPlotColl = RimMainPlotCollection::current()->rftPlotCollection(); for ( auto plot : rftPlotColl->rftPlots() ) { plot->loadDataAndUpdate(); } updateConnectedEditors(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryCaseMainCollection::createSummaryCasesFromFileInfos( const std::vector& summaryHeaderFileInfos, bool readStateFromFirstFile, bool showProgress ) { RimProject* project = RimProject::current(); std::vector sumCases; // Split into two stages to be able to use multi threading // First stage : Create summary case objects // Second stage : Load data { std::unique_ptr progress; if ( showProgress ) { progress = std::make_unique( summaryHeaderFileInfos.size(), "Creating summary cases" ); } for ( const RifSummaryCaseFileResultInfo& fileInfo : summaryHeaderFileInfos ) { QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); auto existingSummaryCase = findTopLevelSummaryCaseFromFileName( fileInfo.summaryFileName() ); if ( !existingSummaryCase ) { const QString& smspecFileName = fileInfo.summaryFileName(); if ( !smspecFileName.isEmpty() ) { RimSummaryCase* newSumCase = nullptr; if ( fileInfo.fileType() == RiaDefines::FileType::SMSPEC ) { auto sumCase = new RimFileSummaryCase(); sumCase->setIncludeRestartFiles( fileInfo.includeRestartFiles() ); newSumCase = sumCase; } else { auto sumCase = new RimCsvSummaryCase(); if ( fileInfo.fileType() == RiaDefines::FileType::STIMPLAN_SUMMARY ) sumCase->setFileType( RimCsvSummaryCase::FileType::STIMPLAN ); else sumCase->setFileType( RimCsvSummaryCase::FileType::REVEAL ); newSumCase = sumCase; } newSumCase->setSummaryHeaderFileName( smspecFileName ); sumCases.push_back( newSumCase ); } else { QString txt = QString( "No UNSMRY file found for %1" ).arg( smspecFileName ); RiaLogging::warning( txt.toStdString() ); } } if ( progress != nullptr ) progress->incrementProgress(); } QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); } // Assign case ids for the complete set of new cases. The cases are not yet part of the project, so the ids must be // assigned in one operation to make sure they are unique. See https://github.com/OPM/ResInsight/issues/14542 project->assignCaseIdsToSummaryCases( sumCases ); RimSummaryCaseMainCollection::loadSummaryCaseData( sumCases, readStateFromFirstFile ); return sumCases; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryCaseMainCollection::uniqueShortNameForCase( RimSummaryCase* summaryCase ) { std::set allAutoShortNames; for ( RimSummaryCase* sumCase : m_cases ) { if ( sumCase && sumCase != summaryCase ) { allAutoShortNames.insert( sumCase->displayCaseName() ); } } bool foundUnique = false; QString caseName = summaryCase->nativeCaseName(); QString shortName; if ( caseName.size() > 2 ) { QString candidate; candidate += caseName[0]; for ( int i = 1; i < caseName.size(); ++i ) { if ( allAutoShortNames.count( candidate + caseName[i] ) == 0 ) { shortName = candidate + caseName[i]; foundUnique = true; break; } } } else { shortName = caseName.left( 2 ); if ( allAutoShortNames.count( shortName ) == 0 ) { foundUnique = true; } } int autoNumber = 0; while ( !foundUnique ) { QString candidate = shortName + QString::number( autoNumber++ ); if ( allAutoShortNames.count( candidate ) == 0 ) { shortName = candidate; foundUnique = true; } } return shortName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::updateAutoShortName() { // This update is required if the file path for the summary case is updated. To be able to produce plots // automatically, the short name must be generated on load // // https://github.com/OPM/ResInsight/issues/7438 auto sumCases = allSummaryCases(); #pragma omp parallel for for ( int cIdx = 0; cIdx < static_cast( sumCases.size() ); ++cIdx ) { auto sumCase = sumCases[cIdx]; sumCase->updateAutoShortName(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::onProjectBeingSaved() { auto sumCases = allSummaryCases(); for ( auto s : sumCases ) { auto fileSumCase = dynamic_cast( s ); if ( fileSumCase ) { fileSumCase->onProjectBeingSaved(); } } for ( auto ensemble : summaryFileSetEnsembles() ) { ensemble->onProjectBeingSaved(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCaseMainCollection::updateEnsembleNames() { std::set key1; std::set key2; auto ensembles = summaryEnsembles(); for ( const auto& ensemble : ensembles ) { const auto keys = ensemble->nameKeys(); key1.insert( keys.first ); key2.insert( keys.second ); } bool useKey1 = key1.size() > 1; bool useKey2 = key2.size() > 1; if ( !useKey1 && !useKey2 ) { useKey2 = true; } std::set existingNames; for ( auto ensemble : ensembles ) { ensemble->setUsePathKey1( useKey1 ); ensemble->setUsePathKey2( useKey2 ); ensemble->updateName( existingNames ); existingNames.insert( ensemble->name() ); } }