mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 07:00:17 -05:00
Closing all summary cases while a delta ensemble is present crashed with a use-after-free. RicCloseSummaryCaseFeature::deleteSummaryCases holds a case list across removeCases and deletes it afterwards, while removeCases made the delta ensemble rebuild and destroy derived cases that are themselves part of that list. The previous fix rewrote the caller list with the surviving cases, which stopped the crash but left the hazard in place for any other caller holding a case list across a removal. Add RimSummaryCaseUpdateBatch, a plain scope object that is ambient for the duration of its scope. Removal now only detaches, and hands the detached cases to the batch. The outermost scope flushes, regenerating the dirty delta ensembles in dependency order first so a chained delta ensemble sees the final state of its source, then destroying the orphans with caf::PdmObjectHandleTools::deleteObjects. A nested batch contributes to the outermost one and never flushes. Orphans are held as guarded pointers, so a case the caller destroyed itself is skipped instead of being destroyed twice. Both contribution points fall back to immediate execution when no batch is active, so call sites that do not open one keep behaving as before. Open a batch in RimSummaryCaseMainCollection::removeCases and in RicCloseSummaryCaseFeature::deleteSummaryCases, which is the outermost of the two and therefore keeps the detached cases alive across its own deleteObjects call. removeCases no longer rewrites the caller list, so it now takes it by const reference.
909 lines
34 KiB
C++
909 lines
34 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 "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 "RifCaseRealizationParametersReader.h"
|
|
#include "RifEclipseSummaryTools.h"
|
|
#include "RifOpmCommonSummary.h"
|
|
#include "RifOpmSummaryTools.h"
|
|
#include "RifSummaryCaseRestartSelector.h"
|
|
#include "Sumo/RimSummaryCaseSumo.h"
|
|
|
|
#ifdef USE_HDF5
|
|
#include "RifHdf5SummaryExporter.h"
|
|
#endif
|
|
|
|
#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 <QCoreApplication>
|
|
#include <QDir>
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
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<QString>& rmsSeedFileCandidate )
|
|
{
|
|
std::shared_ptr<RigCaseRealizationParameters> 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<RigCaseRealizationParameters>();
|
|
}
|
|
|
|
if ( dynamic_cast<RimSummaryCaseSumo*>( &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<RimSummaryCase*> 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<RimDeltaSummaryEnsemble*> derivedEnsembles;
|
|
|
|
// Build a list of derived ensembles that must be updated after delete
|
|
for ( auto ensemble : summaryEnsembles() )
|
|
{
|
|
auto derEnsemble = dynamic_cast<RimDeltaSummaryEnsemble*>( 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<RimSummaryCase*>& 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<int>( childArrayField.size() ) ), object );
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimSummaryCaseMainCollection::moveCase( RimSummaryCase* summaryCase, int destinationIndex )
|
|
{
|
|
moveObjectInChildArrayField( m_cases, summaryCase, destinationIndex );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimSummaryEnsemble* RimSummaryCaseMainCollection::addEnsemble( const std::vector<RimSummaryCase*>& summaryCases,
|
|
const QString& collectionName,
|
|
bool isEnsemble,
|
|
std::function<RimSummaryEnsemble*()> allocator )
|
|
{
|
|
RimSummaryEnsemble* ensemble = allocator();
|
|
if ( !collectionName.isEmpty() ) ensemble->setNameTemplate( collectionName );
|
|
|
|
for ( RimSummaryCase* summaryCase : summaryCases )
|
|
{
|
|
auto currentSummaryCaseCollection = summaryCase->firstAncestorOrThisOfType<RimSummaryEnsemble>();
|
|
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<RimSummaryCase*> RimSummaryCaseMainCollection::allSummaryCases() const
|
|
{
|
|
std::vector<RimSummaryCase*> 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<RimSummaryCase*> RimSummaryCaseMainCollection::topLevelSummaryCases() const
|
|
{
|
|
std::vector<RimSummaryCase*> cases;
|
|
for ( const auto& sumCase : m_cases )
|
|
{
|
|
cases.push_back( sumCase );
|
|
}
|
|
return cases;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<RimSummaryEnsemble*> RimSummaryCaseMainCollection::summaryEnsembles() const
|
|
{
|
|
std::vector<RimSummaryEnsemble*> ensembles;
|
|
for ( const auto& ensemble : m_ensembles )
|
|
{
|
|
ensembles.push_back( ensemble );
|
|
}
|
|
return ensembles;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimSummaryCaseMainCollection::loadAllSummaryCaseData()
|
|
{
|
|
for ( auto ensemble : summaryEnsembles() )
|
|
{
|
|
auto sumCases = ensemble->allSummaryCases();
|
|
|
|
const bool extractStateFromFirstCase = true;
|
|
RimSummaryCaseMainCollection::loadSummaryCaseData( sumCases, extractStateFromFirstCase );
|
|
}
|
|
|
|
std::vector<RimSummaryCase*> 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<RimSummaryCase*>& summaryCases, bool extractStateFromFirstCase )
|
|
{
|
|
std::vector<RimFileSummaryCase*> fileSummaryCases;
|
|
std::vector<RimSummaryCase*> otherSummaryCases;
|
|
|
|
RiaPreferencesSummary* prefs = RiaPreferencesSummary::current();
|
|
|
|
if ( prefs->useMultipleThreadsWhenLoadingSummaryData() )
|
|
{
|
|
for ( auto c : summaryCases )
|
|
{
|
|
auto fileCase = dynamic_cast<RimFileSummaryCase*>( 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<int>( 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<RimFileSummaryCase*>& 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<std::string> headerFileNames;
|
|
std::vector<std::string> 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.baseName() + ".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<QString> 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<int> completedCount = 0;
|
|
|
|
#pragma omp parallel for schedule( dynamic ) if ( canUseMultipleThreads )
|
|
for ( int cIdx = 0; cIdx < static_cast<int>( fileSummaryCases.size() ); ++cIdx )
|
|
{
|
|
RimFileSummaryCase* fileSummaryCase = fileSummaryCases[cIdx];
|
|
if ( fileSummaryCase )
|
|
{
|
|
fileSummaryCase->createSummaryReaderInterfaceThreadSafe( importState, &threadSafeLogger );
|
|
|
|
QString parameterFilePath;
|
|
std::optional<QString> 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<RimSummaryCase*>
|
|
RimSummaryCaseMainCollection::createSummaryCasesFromFileInfos( const std::vector<RifSummaryCaseFileResultInfo>& summaryHeaderFileInfos,
|
|
bool readStateFromFirstFile,
|
|
bool showProgress )
|
|
{
|
|
RimProject* project = RimProject::current();
|
|
|
|
std::vector<RimSummaryCase*> 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<caf::ProgressInfo> progress;
|
|
|
|
if ( showProgress )
|
|
{
|
|
progress = std::make_unique<caf::ProgressInfo>( 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 );
|
|
project->assignCaseIdToSummaryCase( newSumCase );
|
|
|
|
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 );
|
|
}
|
|
|
|
RimSummaryCaseMainCollection::loadSummaryCaseData( sumCases, readStateFromFirstFile );
|
|
|
|
return sumCases;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RimSummaryCaseMainCollection::uniqueShortNameForCase( RimSummaryCase* summaryCase )
|
|
{
|
|
std::set<QString> 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<int>( sumCases.size() ); ++cIdx )
|
|
{
|
|
auto sumCase = sumCases[cIdx];
|
|
sumCase->updateAutoShortName();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimSummaryCaseMainCollection::onProjectBeingSaved()
|
|
{
|
|
auto sumCases = allSummaryCases();
|
|
for ( auto s : sumCases )
|
|
{
|
|
auto fileSumCase = dynamic_cast<RimFileSummaryCase*>( s );
|
|
if ( fileSumCase )
|
|
{
|
|
fileSumCase->onProjectBeingSaved();
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimSummaryCaseMainCollection::updateEnsembleNames()
|
|
{
|
|
std::set<std::string> key1;
|
|
std::set<std::string> 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<QString> existingNames;
|
|
for ( auto ensemble : ensembles )
|
|
{
|
|
ensemble->setUsePathKey1( useKey1 );
|
|
ensemble->setUsePathKey2( useKey2 );
|
|
ensemble->updateName( existingNames );
|
|
existingNames.insert( ensemble->name() );
|
|
}
|
|
}
|