///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2025 Equinor 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 "RimOpmFlowJob.h" #include "RiaGuiApplication.h" #include "RiaImportEclipseCaseTools.h" #include "RiaLogging.h" #include "RiaPreferencesOpm.h" #include "RiaQStringFormatter.h" #include "RiaWslTools.h" #include "CompletionExportCommands/RicExportCompletionDataSettingsUi.h" #include "CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h" #include "CompletionExportCommands/RicWellPathExportMswTableData.h" #include "EclipseCommands/RicCreateGridCaseEnsemblesFromFilesFeature.h" #include "JobCommands/RicRunJobFeature.h" #include "JobCommands/RicStopJobFeature.h" #include "RifOpmFlowDeckFile.h" #include "CompletionsMsw/RigMswTableData.h" #include "Ensemble/RimSummaryFileSetEnsemble.h" #include "EnsembleFileSet/RimEnsembleFileSet.h" #include "EnsembleFileSet/RimEnsembleFileSetTools.h" #include "RimCase.h" #include "RimDeckPositionDlg.h" #include "RimEclipseCase.h" #include "RimEclipseCaseCollection.h" #include "RimEclipseCaseEnsemble.h" #include "RimFishbones.h" #include "RimJobWellSettings.h" #include "RimKeywordFactory.h" #include "RimKeywordWconinje.h" #include "RimKeywordWconprod.h" #include "RimOilField.h" #include "RimOpmFlowJobSettings.h" #include "RimPerforationInterval.h" #include "RimProject.h" #include "RimReloadCaseTools.h" #include "RimSummaryEnsemble.h" #include "RimTools.h" #include "RimWellPath.h" #include "RimWellPathCompletionSettings.h" #include "RimWellPathFracture.h" #include "Riu3DMainWindowTools.h" #include "cafPdmUiButton.h" #include "cafPdmUiCheckBoxEditor.h" #include "cafPdmUiComboBoxEditor.h" #include "cafPdmUiFilePathEditor.h" #include "cafProgressInfo.h" #include "opm/input/eclipse/Deck/DeckKeyword.hpp" #include #include #include #include CAF_PDM_SOURCE_INIT( RimOpmFlowJob, "OpmFlowJob" ); namespace caf { template <> void caf::AppEnum::setUp() { addItem( RimOpmFlowJob::DateAppendType::ADD_DAYS, "AppendDays", "Day(s)" ); addItem( RimOpmFlowJob::DateAppendType::ADD_MONTHS, "AppendMonths", "Month(s)" ); setDefault( RimOpmFlowJob::DateAppendType::ADD_MONTHS ); } } // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimOpmFlowJob::RimOpmFlowJob() : m_fileDeckHasDates( false ) , m_fileDeckIsRestart( false ) , m_startStepForProgress( -1 ) , jobCompleted( this ) , progressUpdate( this ) { CAF_PDM_InitObject( "Opm Flow Simulation", ":/opm.png" ); CAF_PDM_InitFieldNoDefault( &m_deckFileName, "DeckFile", "Input Data File" ); m_deckFileName.uiCapability()->setUiReadOnly( true ); CAF_PDM_InitFieldNoDefault( &m_workDir, "WorkDirectory", "Working Folder" ); m_workDir.uiCapability()->setAttribute( caf::PdmUiFilePathEditor::Keys::SELECT_DIRECTORY, true ); CAF_PDM_InitFieldNoDefault( &m_wellPath, "WellPath", "Well Path for New Well" ); CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case for Well Data", "", "Eclipse Case to use for Well Data" ); CAF_PDM_InitFieldNoDefault( &m_gridEnsemble, "GridEnsemble", "Grid Ensemble" ); CAF_PDM_InitFieldNoDefault( &m_summaryEnsemble, "SummaryEnsemble", "Summary Ensemble" ); CAF_PDM_InitField( &m_pauseBeforeRun, "PauseBeforeRun", false, "Pause before running OPM Flow" ); CAF_PDM_InitField( &m_addNewWell, "AddNewWell", true, "Add New Well" ); CAF_PDM_InitField( &m_openWellDeckPosition, "OpenWellDeckPosition", -1, "Open Well at Keyword Index" ); CAF_PDM_InitField( &m_includeMSWData, "IncludeMswData", false, "Include MSW Data" ); CAF_PDM_InitField( &m_addToEnsemble, "AddToEnsemble", false, "Add Runs to Ensemble" ); CAF_PDM_InitField( &m_useRestart, "UseRestart", false, "Restart Simulation at Well Open Date" ); CAF_PDM_InitField( &m_currentRunId, "CurrentRunID", 0, "Current Ensemble Run ID" ); m_currentRunId.uiCapability()->setUiReadOnly( true ); CAF_PDM_InitFieldNoDefault( &m_wellGroupName, "WellGroupName", "Well Group Name" ); m_wellGroupName.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); m_wellGroupName.uiCapability()->setAttribute( caf::PdmUiComboBoxEditor::Keys::ENABLE_EDITABLE_CONTENT, true ); m_wellGroupName.uiCapability()->setAttribute( caf::PdmUiComboBoxEditor::Keys::ENABLE_AUTO_COMPLETE, false ); m_wellGroupName.uiCapability()->setAttribute( caf::PdmUiComboBoxEditor::Keys::ADJUST_WIDTH_TO_CONTENTS, true ); CAF_PDM_InitField( &m_wellOpenType, "WellOpenType", caf::AppEnum( RimJobWellSettings::WellOpenType::OPEN_AT_DATE ), "Open Well" ); CAF_PDM_InitField( &m_wellOpenKeyword, "WellOpenKeyword", QString( "WCONPROD" ), "Open Well Keyword" ); m_wellOpenKeyword.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); m_wellOpenKeyword.xmlCapability()->disableIO(); CAF_PDM_InitFieldNoDefault( &m_wconprodKeyword, "WconprodKeyword", "WCONPROD Settings" ); m_wconprodKeyword = new RimKeywordWconprod(); m_wconprodKeyword.uiCapability()->setUiTreeChildrenHidden( true ); CAF_PDM_InitFieldNoDefault( &m_wconinjeKeyword, "WconinjeKeyword", "WCONINJE Settings" ); m_wconinjeKeyword = new RimKeywordWconinje(); m_wconinjeKeyword.uiCapability()->setUiTreeChildrenHidden( true ); CAF_PDM_InitFieldNoDefault( &m_jobSettings, "JobSettings", "Opm Flow Settings" ); m_jobSettings = RiaPreferencesOpm::current()->createDefaultJobSettings(); m_jobSettings.uiCapability()->setUiTreeChildrenHidden( true ); CAF_PDM_InitField( &m_openTimeStep, "OpenTimeStep", 0, " " ); CAF_PDM_InitField( &m_endTimeStep, "EndTimeStep", 0, " " ); CAF_PDM_InitField( &m_endTimeStepEnabled, "EndTimeStepEnabled", false, "Stop Simulation at Date" ); CAF_PDM_InitField( &m_appendNewDates, "AppendNewDates", false, "Append Extra Dates" ); CAF_PDM_InitField( &m_newDatesInterval, "NewDatesInterval", 1, "Interval" ); CAF_PDM_InitField( &m_numberOfNewDates, "NumberOfNewDates", 12, "Number of Dates to Append" ); CAF_PDM_InitField( &m_dateAppendType, "DateAppendType", caf::AppEnum( DateAppendType::ADD_MONTHS ), " " ); CAF_PDM_InitField( &m_autoLoadResults, "AutoLoadResults", true, "Automatic loading of results from job" ); m_autoLoadResults.uiCapability()->setUiHidden( true ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addToEnsemble ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_pauseBeforeRun ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_useRestart ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_includeMSWData ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addNewWell ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_endTimeStepEnabled ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_appendNewDates ); setDeletable( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimOpmFlowJob::~RimOpmFlowJob() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::initAfterRead() { openDeckFile(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::processLogOutput( const QString& logLine ) { // progress output parsing // Example log lines: // Report step 757/773 at day 9466/10958, date = 01-Dec-2025 // Report step 758/773 at day 9497/10958, date = 01-Jan-2026 if ( logLine.startsWith( "Report step" ) ) { auto parts = logLine.split( ' ', Qt::SkipEmptyParts ); if ( parts.size() >= 4 ) { auto stepPart = parts[2]; // 756/773 auto stepParts = stepPart.split( '/' ); if ( stepParts.size() == 2 ) { bool ok1 = false; bool ok2 = false; int currentStep = stepParts[0].toInt( &ok1 ); if ( ok1 && ( m_startStepForProgress < 0 ) ) { m_startStepForProgress = currentStep; } int totalSteps = stepParts[1].toInt( &ok2 ); if ( ok1 && ok2 ) { auto noSteps = totalSteps - m_startStepForProgress; if ( noSteps > 0 ) { double perc = ( (double)( currentStep - m_startStepForProgress ) / (double)( noSteps ) ) * 100.0; perc = std::max( 0.0, perc ); perc = std::min( perc, 100.0 ); onProgress( perc ); } } } } } else if ( logLine.startsWith( "Warning" ) ) { m_warningsDetected++; } else if ( logLine.startsWith( "Problem" ) ) { m_warningsDetected++; } else if ( logLine.startsWith( "Error" ) ) { m_errorsDetected++; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimOpmFlowJob::matchesKeyValue( const QString& key, const QString& value ) const { if ( key == jobInputFileKey() ) { return ( m_deckFileName() == value ); } return false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { if ( isRunning() ) { m_workDir.uiCapability()->setUiReadOnly( true ); auto runGrp = uiOrdering.addNewGroup( "Running" ); runGrp->add( &m_workDir ); auto stopButton = runGrp->addNewButton( "Stop", [this]() { RicStopJobFeature::stopJob( this ); } ); stopButton->setUiIconFromResourceString( ":/stop.svg" ); stopButton->setAlignment( Qt::AlignCenter ); uiOrdering.skipRemainingFields(); return; } if ( isChildJob() ) { m_workDir.uiCapability()->setUiReadOnly( true ); uiOrdering.add( &m_workDir ); uiOrdering.skipRemainingFields(); return; } m_workDir.uiCapability()->setUiReadOnly( false ); auto genGrp = uiOrdering.addNewGroup( "General" ); genGrp->add( nameField() ); genGrp->add( &m_deckFileName ); genGrp->add( &m_workDir ); genGrp->add( &m_addToEnsemble ); auto runButton = genGrp->addNewButton( "Run", [this]() { RicRunJobFeature::runJob( this ); } ); runButton->setUiIconFromResourceString( ":/Play.svg" ); runButton->setAlignment( Qt::AlignCenter ); if ( m_eclipseCase() == nullptr ) { m_addNewWell = false; } else { auto wellGrp = uiOrdering.addNewGroup( "New Well Settings" ); wellGrp->add( &m_addNewWell ); wellGrp->add( &m_eclipseCase ); if ( m_addNewWell() ) { wellGrp->add( &m_wellPath ); wellGrp->add( &m_wellGroupName ); wellGrp->add( &m_wellOpenKeyword ); if ( m_wellOpenKeyword() == "WCONPROD" ) { auto wconGrp = wellGrp->addNewGroup( "WCONPROD Settings" ); m_wconprodKeyword->uiOrdering( wconGrp ); wconGrp->setCollapsedByDefault(); } else { auto wconGrp = wellGrp->addNewGroup( "WCONINJE Settings" ); m_wconinjeKeyword->uiOrdering( wconGrp ); wconGrp->setCollapsedByDefault(); } wellGrp->add( &m_wellOpenType ); bool createOpenPostionButton = false; if ( m_fileDeckHasDates ) { if ( m_wellOpenType() == RimJobWellSettings::WellOpenType::OPEN_AT_DATE ) { wellGrp->add( &m_openTimeStep ); if ( !m_fileDeckIsRestart ) { wellGrp->add( &m_useRestart ); } else { m_useRestart = false; } if ( m_eclipseCase() ) wellGrp->add( &m_includeMSWData ); } else if ( m_wellOpenType == RimJobWellSettings::WellOpenType::OPEN_BY_POSITION ) { createOpenPostionButton = true; } m_wellOpenType.uiCapability()->setUiReadOnly( false ); } else { createOpenPostionButton = true; m_wellOpenType = RimJobWellSettings::WellOpenType::OPEN_BY_POSITION; m_wellOpenType.uiCapability()->setUiReadOnly( true ); } if ( createOpenPostionButton ) { wellGrp->addNewButton( "Select Open Keyword Position", [this]() { selectOpenWellPosition(); } ); } } } if ( m_fileDeckHasDates ) { auto dateGrp = uiOrdering.addNewGroup( "Date Settings" ); dateGrp->setCollapsedByDefault(); dateGrp->add( &m_endTimeStepEnabled ); if ( m_endTimeStepEnabled() ) { dateGrp->add( &m_endTimeStep ); } dateGrp->add( &m_appendNewDates ); if ( m_appendNewDates() ) { dateGrp->add( &m_numberOfNewDates ); dateGrp->add( &m_newDatesInterval ); dateGrp->appendToRow( &m_dateAppendType ); } } auto opmGrp = uiOrdering.addNewGroup( "OPM Flow" ); opmGrp->setCollapsedByDefault(); opmGrp->add( &m_pauseBeforeRun ); m_jobSettings->uiOrdering( opmGrp, false /* expand by default */ ); auto advGrp = uiOrdering.addNewGroup( "Advanced" ); advGrp->setCollapsedByDefault(); advGrp->add( &m_currentRunId ); advGrp->addNewButton( "Reset Ensemble Run Id", [this]() { resetEnsembleRunId(); } ); uiOrdering.skipRemainingFields(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimOpmFlowJob::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options; if ( fieldNeedingOptions == &m_wellPath ) { RimTools::wellPathOptionItems( &options ); } else if ( ( fieldNeedingOptions == &m_openTimeStep ) || ( fieldNeedingOptions == &m_endTimeStep ) ) { if ( openDeckFile() ) { auto timeStepNames = dateStrings(); for ( int i = 0; i < static_cast( timeStepNames.size() - 1 ); ++i ) { options.push_back( caf::PdmOptionItemInfo( timeStepNames[i], QVariant::fromValue( i ) ) ); } } } else if ( fieldNeedingOptions == &m_wellOpenKeyword ) { options.push_back( caf::PdmOptionItemInfo( "WCONPROD", QVariant::fromValue( QString( "WCONPROD" ) ) ) ); options.push_back( caf::PdmOptionItemInfo( "WCONINJE", QVariant::fromValue( QString( "WCONINJE" ) ) ) ); } else if ( fieldNeedingOptions == &m_wellGroupName ) { for ( auto& grp : wellgroupsInFileDeck() ) { options.push_back( caf::PdmOptionItemInfo( grp, QVariant::fromValue( grp ) ) ); } } else if ( fieldNeedingOptions == &m_eclipseCase ) { RimTools::eclipseCaseOptionItems( &options ); } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimOpmFlowJob::dateStrings() { std::vector dateStrs; for ( auto& dt : dateTimes() ) { dateStrs.push_back( dt.toString( "dd-MMM-yyyy (HH:mm)" ) ); } return dateStrs; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimOpmFlowJob::dateTimes() { auto dates = datesInFileDeck(); auto newDates = addedDateTimes(); dates.insert( dates.end(), newDates.begin(), newDates.end() ); return dates; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimOpmFlowJob::addedDateTimes() { if ( !m_appendNewDates() || !m_fileDeckHasDates ) return {}; auto existingDates = datesInFileDeck(); auto& startDate = existingDates.back(); std::vector newDates; for ( auto dt = startDate; newDates.size() < static_cast( m_numberOfNewDates() ); ) { if ( m_dateAppendType() == DateAppendType::ADD_DAYS ) { dt = dt.addDays( m_newDatesInterval() ); } else if ( m_dateAppendType() == DateAppendType::ADD_MONTHS ) { dt = dt.addMonths( m_newDatesInterval() ); } newDates.push_back( dt ); } return newDates; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { if ( changedField == &m_wellPath ) { if ( ( m_wellPath() != nullptr ) && ( m_wellPath->completionSettings() != nullptr ) ) { auto wellSettings = m_wellPath->completionSettings(); if ( !wellSettings->groupName().isEmpty() && m_wellGroupName().isEmpty() ) { m_wellGroupName = wellSettings->groupName(); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::setWorkingDirectory( QString workDir ) { m_workDir = workDir; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::mainWorkingDirectory() const { return m_workDir().path(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::setEclipseCase( RimEclipseCase* eCase ) { if ( eCase == nullptr ) { m_deckFileName.setValue( QString() ); closeDeckFile(); return; } QFileInfo fi( eCase->gridFileName() ); m_deckFileName.setValue( fi.absolutePath() + "/" + fi.completeBaseName() + deckExtension() ); m_eclipseCase = eCase; closeDeckFile(); openDeckFile(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::setInputDataFile( QString filename ) { m_deckFileName.setValue( filename ); closeDeckFile(); openDeckFile(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::setJobSettings( RimOpmFlowJobSettings* jobSettings ) { m_jobSettings = jobSettings->copyObject(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimOpmFlowJob::openDeckFile() { if ( m_deckFile.get() == nullptr ) { m_deckFile = std::make_unique(); bool deckLoadOk = false; try { auto loadResult = m_deckFile->loadDeck( m_deckFileName().path().toStdString() ); if ( loadResult.has_value() ) { m_fileDeckHasDates = m_deckFile->hasDatesKeyword(); m_fileDeckIsRestart = m_deckFile->isRestartFile(); deckLoadOk = true; } else { RiaLogging::error( QString( "Failed to open %1. Error: %2." ) .arg( m_deckFileName().path() ) .arg( QString::fromStdString( loadResult.error() ) ) .toStdString() ); deckLoadOk = false; } } catch ( std::filesystem::filesystem_error& ) { deckLoadOk = false; RiaLogging::error( std::format( "Failed to open {}, possibly unsupported or incorrect format.", m_deckFileName().path() ) ); } if ( !deckLoadOk ) { m_fileDeckHasDates = false; m_fileDeckIsRestart = false; closeDeckFile(); } } return m_deckFile.get() != nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::closeDeckFile() { m_deckFile.reset(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimOpmFlowJob::copyUnrstFileToWorkDir() { QFileInfo fi( m_deckFileName().path() ); QString unrstName = fi.absolutePath() + "/" + fi.baseName() + ".UNRST"; if ( QFile::exists( unrstName ) ) { QString dstName = workingDirectory() + "/" + restartDeckName() + ".UNRST"; if ( dstName != unrstName ) { QFile::copy( unrstName, dstName ); } return true; } return false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::workingDirectory() const { if ( !m_addToEnsemble() ) { return m_workDir().path(); } else { return QString( "%1/run-%2" ).arg( m_workDir().path() ).arg( m_currentRunId() ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::deckName() { auto deckName = baseDeckName(); if ( m_addToEnsemble() ) { deckName = deckName + "-" + QString::number( m_currentRunId() ); } return deckName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::baseDeckName() { auto baseName = name(); baseName.replace( ' ', '_' ); baseName = baseName.toUpper(); return baseName; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::restartDeckName() { return inputDeckName() + "_RST"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::inputDeckName() const { QFileInfo fi( m_deckFileName().path() ); return fi.completeBaseName(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::deckExtension() const { return ".DATA"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RimOpmFlowJob::numberOfProcesses() { if ( !RiaPreferencesOpm::current()->useMpi() ) return 1; return m_jobSettings()->mpiProcesses(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QStringList RimOpmFlowJob::command() { QStringList cmd; QString workDir = workingDirectory(); if ( workDir.isEmpty() ) return cmd; QString dataFile = workDir + "/" + deckName(); if ( !QFile::exists( dataFile + deckExtension() ) ) return cmd; auto opmPref = RiaPreferencesOpm::current(); if ( opmPref->useWsl() ) { workDir = RiaWslTools::convertToWslPath( workDir ); dataFile = RiaWslTools::convertToWslPath( dataFile ); } if ( opmPref->useMpi() ) { cmd.append( opmPref->mpirunCommand() ); cmd.append( QString( "-np" ) ); cmd.append( QString( "%1" ).arg( numberOfProcesses() ) ); } cmd.append( opmPref->opmFlowCommand() ); cmd.append( QString( "--output-dir=%1" ).arg( workDir ) ); cmd.append( QString( "--ecl-deck-file-name=%1" ).arg( dataFile ) ); cmd.append( m_jobSettings->commandLineOptions( workingDirectory(), workDir ) ); return cmd; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::map RimOpmFlowJob::environment() { auto opmPref = RiaPreferencesOpm::current(); if ( opmPref->useWsl() ) { return RiaWslTools::wslEnvironmentVariables(); } return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimOpmFlowJob::onPrepare() { if ( name().isEmpty() ) { RiaLogging::error( "Please set a name for the OPM Flow Job." ); return false; } // reload file deck to make sure we start with the original closeDeckFile(); if ( !openDeckFile() ) { RiaLogging::error( "Unable to open input DATA file " + m_deckFileName().path().toStdString() ); return false; } // add extra date keywords if ( m_appendNewDates() && m_fileDeckHasDates ) { std::vector newDates; for ( auto& dt : addedDateTimes() ) { newDates.push_back( dt.toSecsSinceEpoch() ); } m_deckFile->appendDateKeywords( newDates ); } // add a new well? if ( m_addNewWell() ) { if ( m_wellPath == nullptr ) { RiaLogging::error( "Please select a well path to use." ); return false; } if ( m_wellPath->completionSettings() == nullptr ) { RiaLogging::error( "The selected well path has no completions defined." ); return false; } auto wellNameInDeck = m_wellPath->completionSettings()->wellName(); if ( wellNameInDeck.isEmpty() ) { RiaLogging::error( "Selected Well Path does not have a WELL NAME set, please check the completion settings." ); return false; } if ( m_wellGroupName().isEmpty() ) { RiaLogging::error( "Please set the well group name." ); return false; } m_wellPath->completionSettings()->setGroupName( m_wellGroupName() ); // well data needs the case to be open, this is not necessarily true for ensemble bool needToOpenCase = !m_eclipseCase->isReservoirCaseOpen(); if ( needToOpenCase ) { m_eclipseCase->ensureReservoirCaseIsOpen(); } int mergePosition = mergeBasicWellSettings(); if ( mergePosition < 0 ) { if ( needToOpenCase ) m_eclipseCase->closeReservoirCase(); RiaLogging::error( "Unable to merge new well data into DATA file. Please check file format." ); return false; } if ( ( m_includeMSWData ) && ( m_wellOpenType == RimJobWellSettings::WellOpenType::OPEN_AT_DATE ) ) { mergePosition = mergeMswData( mergePosition ); if ( mergePosition < 0 ) { if ( needToOpenCase ) m_eclipseCase->closeReservoirCase(); RiaLogging::error( "Failed to merge MSW data into file deck." ); return false; } } // close cases that was previously closed to save memory if ( needToOpenCase ) m_eclipseCase->closeReservoirCase(); Opm::DeckKeyword openKeyword = ( m_wellOpenKeyword() == "WCONPROD" ) ? m_wconprodKeyword->keyword( wellNameInDeck ) : m_wconinjeKeyword->keyword( wellNameInDeck ); // open new well at selected timestep if ( m_wellOpenType == RimJobWellSettings::WellOpenType::OPEN_AT_DATE ) { if ( !m_deckFile->openWellAtTimeStep( m_openTimeStep(), openKeyword ) ) { RiaLogging::error( "Unable to open new well at selected timestep in DATA file." ); return false; } } else { if ( !m_deckFile->openWellAtDeckPosition( mergePosition, openKeyword ) ) { RiaLogging::error( "Unable to open new well at selected position in DATA file." ); return false; } } } if ( m_useRestart() ) { if ( !copyUnrstFileToWorkDir() ) { RiaLogging::error( "Unable to locate UNRST file from input case." ); return false; } if ( !m_deckFile->restartAtTimeStep( m_openTimeStep(), restartDeckName().toStdString() ) ) { RiaLogging::error( "Unable to insert restart keywords in DATA file." ); return false; } } if ( m_endTimeStepEnabled() ) { if ( !m_deckFile->stopAtTimeStep( m_endTimeStep() ) ) { RiaLogging::error( "Unable to insert END keyword in DATA file." ); return false; } } caf::ProgressInfo saveProgress( 1, "Saving updated information to DATA file.", false ); // save DATA file to working folder bool saveOk = m_deckFile->saveDeck( workingDirectory().toStdString(), deckName().toStdString() + deckExtension().toStdString() ); closeDeckFile(); return saveOk; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimOpmFlowJob::onRun() { if ( m_pauseBeforeRun() ) { QString infoText = "Input parameter files can now be found in the working folder:"; infoText += " \"" + workingDirectory() + "\"\n"; infoText += "\nClick OK to run the Opm Flow simulation."; auto parent = RiaGuiApplication::widgetToUseAsParent(); auto reply = QMessageBox::information( parent, "Opm Flow simulation", infoText, QMessageBox::Ok | QMessageBox::Cancel ); if ( reply != QMessageBox::Ok ) return false; } return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::onProgress( double percentageDone ) { m_percentageDone = percentageDone; updateConnectedEditors(); progressUpdate.send( percentageDone ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimOpmFlowJob::shouldUseWsl() { return RiaPreferencesOpm::current()->useWsl(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::onCompleted( bool success ) { if ( success && m_autoLoadResults() ) { loadResults(); } jobCompleted.send( success ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::loadResults() { QString outputEgridFileName = workingDirectory() + "/" + deckName() + ".EGRID"; if ( !QFile::exists( outputEgridFileName ) ) return; if ( m_addToEnsemble() ) { // grid ensemble if ( m_gridEnsemble() == nullptr ) { m_gridEnsemble = RicCreateGridCaseEnsemblesFromFilesFeature::importSingleGridCaseEnsemble( QStringList( outputEgridFileName ) ); } else { if ( auto rimResultCase = m_gridEnsemble->findByFileName( outputEgridFileName ) ) { rimResultCase->reloadEclipseGridFile(); } else { rimResultCase = RicCreateGridCaseEnsemblesFromFilesFeature::importSingleGridCase( outputEgridFileName ); m_gridEnsemble->addCase( rimResultCase ); } for ( auto gridCase : m_gridEnsemble->cases() ) { gridCase->updateAutoShortName(); } RimProject::current()->activeOilField()->analysisModels()->updateConnectedEditors(); } // summary ensemble if ( m_summaryEnsemble() == nullptr ) { QString pattern = m_workDir().path() + "/run-*/" + baseDeckName() + "-*"; auto fileSet = RimEnsembleFileSetTools::createEnsembleFileSetFromOpm( pattern, name() ); auto ensembles = RimEnsembleFileSetTools::createSummaryEnsemblesFromFileSets( { fileSet } ); if ( !ensembles.empty() ) { m_summaryEnsemble = ensembles[0]; } } else { m_summaryEnsemble->reloadCases(); } m_currentRunId = m_currentRunId + 1; } else { if ( auto existingCase = findExistingCase( outputEgridFileName ) ) { RimReloadCaseTools::reloadEclipseGridAndSummary( existingCase ); existingCase->setCustomCaseName( name() ); existingCase->updateConnectedEditors(); } else { QStringList files( outputEgridFileName ); RiaImportEclipseCaseTools::FileCaseIdMap newCaseFiles; if ( RiaImportEclipseCaseTools::openEclipseCasesFromFile( files, true /*create view*/, &newCaseFiles, false /* dialog */ ) ) { if ( auto newCase = findExistingCase( outputEgridFileName ) ) { newCase->setCustomCaseName( name() ); newCase->updateConnectedEditors(); if ( m_eclipseCase() == nullptr ) { m_eclipseCase = newCase; } Riu3DMainWindowTools::selectAsCurrentItem( newCase ); Riu3DMainWindowTools::setExpanded( newCase, true ); } } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseCase* RimOpmFlowJob::findExistingCase( QString filename ) { if ( RimProject* proj = RimProject::current() ) { for ( RimCase* c : proj->allGridCases() ) { RimEclipseCase* eclipseCase = dynamic_cast( c ); if ( eclipseCase && ( eclipseCase->gridFileName() == filename ) ) { return eclipseCase; } } } return nullptr; } //-------------------------------------------------------------------------------------------------- /// Returns value < 0 if failure to merge //-------------------------------------------------------------------------------------------------- int RimOpmFlowJob::mergeBasicWellSettings() { const int failure = -1; if ( m_eclipseCase() == nullptr || m_wellPath() == nullptr || m_wellPath()->completionSettings() == nullptr || m_eclipseCase()->eclipseCaseData() == nullptr ) { RiaLogging::error( "Cannot create COMPDAT keyword - check case, wellpath and completion settings data." ); return failure; } auto compdata = RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( m_wellPath(), m_eclipseCase() ); auto wellName = m_wellPath()->completionSettings()->wellNameForExport().toStdString(); int mergePosition = m_openWellDeckPosition(); auto compdatKw = RimKeywordFactory::compdatKeyword( compdata, wellName ); auto wpimultKw = RimKeywordFactory::wpimultKeyword( compdata, wellName ); auto complumpKw = RimKeywordFactory::complumpKeyword( compdata, wellName ); auto welspecsKw = RimKeywordFactory::welspecsKeyword( m_wellGroupName().toStdString(), m_eclipseCase(), m_wellPath() ); if ( compdatKw.empty() ) { RiaLogging::warning( "Failed to create COMPDAT keyword for selected well path. Do you have a valid case selected?" ); } if ( welspecsKw.empty() ) { RiaLogging::error( "Failed to create WELSPECS keyword for selected well path. Do you have a valid case selected?" ); return failure; } if ( m_wellOpenType == RimJobWellSettings::WellOpenType::OPEN_AT_DATE ) { // reverse order for correct insertion order if ( !complumpKw.empty() ) { if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), complumpKw ) ) return failure; } if ( !wpimultKw.empty() ) { if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wpimultKw ) ) return failure; } if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), compdatKw ) ) return failure; if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), welspecsKw ) ) return failure; mergePosition = 0; } else { mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, welspecsKw ); if ( mergePosition < 0 ) return failure; mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, compdatKw ); if ( mergePosition < 0 ) return failure; if ( !wpimultKw.empty() ) { mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wpimultKw ); if ( mergePosition < 0 ) return failure; } if ( !complumpKw.empty() ) { mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, complumpKw ); if ( mergePosition < 0 ) return failure; } } // increase wells and connections in welldims to make sure they are big enough auto additionalConnections = (int)compdatKw.size(); auto welldims = m_deckFile->welldims(); if ( ( welldims.size() < 4 ) || !m_deckFile->setWelldims( (int)welldims[0] + 1, (int)( welldims[1] + additionalConnections ), (int)welldims[2] + 1, (int)welldims[3] + 1 ) ) { RiaLogging::error( "Failed to update WELLDIMS keyword in DATA file, is it missing?" ); return failure; } return mergePosition; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RimOpmFlowJob::mergeMswData( int mergePosition ) { const int failure = -1; auto exportDate = RicWellPathExportCompletionDataFeatureImpl::exportDateForTimeStep( m_eclipseCase(), 0 ); auto mswDataResult = RicWellPathExportMswTableData::extractSingleWellMswData( m_eclipseCase(), m_wellPath(), true, RicWellPathExportMswTableData::CompletionType::ALL, exportDate ); if ( !mswDataResult.has_value() ) { RiaLogging::error( mswDataResult.error() ); return failure; } int maxSegNum = 0; int maxBranches = 0; auto welsegsKw = RimKeywordFactory::welsegsKeyword( mswDataResult.value(), maxSegNum, maxBranches ); auto compsegsKw = RimKeywordFactory::compsegsKeyword( mswDataResult.value() ); auto wsegvalvKw = RimKeywordFactory::wsegvalvKeyword( mswDataResult.value() ); auto wsegaicdKw = RimKeywordFactory::wsegaicdKeyword( mswDataResult.value() ); auto wsegsicdKw = RimKeywordFactory::wsegsicdKeyword( mswDataResult.value() ); if ( compsegsKw.empty() ) { RiaLogging::warning( "Unable to create COMPSEGS keyword from MSW data, skipping MSW export. Is the wellpath outside the grid?" ); return ( m_wellOpenType() == RimJobWellSettings::WellOpenType::OPEN_AT_DATE ) ? 0 : mergePosition; } if ( welsegsKw.empty() ) { RiaLogging::warning( "Unable to create WELSEGS keyword from MSW data. Is the wellpath outside the grid?" ); } if ( m_wellOpenType() == RimJobWellSettings::WellOpenType::OPEN_AT_DATE ) { // make sure we insert after COMPDAT kw if ( !m_deckFile->addKeywordAtTimeStep( m_openTimeStep(), welsegsKw, "COMPDAT" ) ) return failure; if ( !m_deckFile->addKeywordAtTimeStep( m_openTimeStep(), compsegsKw, welsegsKw.name() ) ) return failure; if ( !wsegvalvKw.empty() ) { if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wsegvalvKw, compsegsKw.name() ) ) return failure; } if ( !wsegaicdKw.empty() ) { if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wsegaicdKw, compsegsKw.name() ) ) return failure; } if ( !wsegsicdKw.empty() ) { if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wsegsicdKw, compsegsKw.name() ) ) return failure; } mergePosition = 0; } else { mergePosition = m_deckFile->addKeywordAtPosition( mergePosition, welsegsKw ); if ( mergePosition < 0 ) return failure; mergePosition++; mergePosition = m_deckFile->addKeywordAtPosition( mergePosition, compsegsKw ); if ( mergePosition < 0 ) return failure; mergePosition++; if ( !wsegvalvKw.empty() ) { mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wsegvalvKw ); if ( mergePosition < 0 ) return failure; mergePosition++; } if ( !wsegaicdKw.empty() ) { mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wsegaicdKw ); if ( mergePosition < 0 ) return failure; mergePosition++; } if ( !wsegsicdKw.empty() ) { mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wsegsicdKw ); if ( mergePosition < 0 ) return failure; mergePosition++; } } int branches = (int)m_wellPath->allWellPathLaterals().size() + 1; // wsegdims contains: max segmented wells, max segments, max branches auto wsegdims = m_deckFile->wsegdims(); int maxSegWells = wsegdims[0] + 1; // we have added one well maxSegNum = std::max( wsegdims[1], maxSegNum ); maxBranches = std::max( branches, maxBranches ); maxBranches = std::max( wsegdims[2], maxBranches ); if ( !m_deckFile->setWsegdims( maxSegWells, maxSegNum, maxBranches ) ) { RiaLogging::error( "Failed to update WSEGDIMS keyword in DATA file." ); return failure; } return mergePosition; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::selectOpenWellPosition() { if ( !openDeckFile() ) return; std::vector> kwVec; auto kws = m_deckFile->keywords(); int i = 0; for ( auto& kw : kws ) { kwVec.push_back( std::make_pair( i++, QString::fromStdString( kw ) ) ); } m_openWellDeckPosition = RimDeckPositionDlg::askForPosition( RiaGuiApplication::widgetToUseAsParent(), kwVec, "--- Open New Well HERE ---", m_openWellDeckPosition ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::resetEnsembleRunId() { if ( QMessageBox::information( RiaGuiApplication::widgetToUseAsParent(), "Opm Flow Job", "Do you want to reset the ensemble run ID to 0?", QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes ) { m_currentRunId = 0; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::initAfterCopy() { m_currentRunId = 0; m_gridEnsemble = nullptr; m_summaryEnsemble = nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimOpmFlowJob::datesInFileDeck() { std::vector dates; if ( openDeckFile() ) { for ( auto tt : m_deckFile->dates() ) { dates.push_back( QDateTime::fromSecsSinceEpoch( tt ) ); } } return dates; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimOpmFlowJob::wellgroupsInFileDeck() { std::vector groups; if ( openDeckFile() ) { for ( auto& grp : m_deckFile->wellGroupsInFile() ) { groups.push_back( QString::fromStdString( grp ) ); } } return groups; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimOpmFlowJob::jobInputFileKey() { return "OpmFlowInputFile"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::setJobWellSettings( RimJobWellSettings* jobWellSettings ) { if ( jobWellSettings == nullptr ) return; m_addNewWell = jobWellSettings->addNewWell(); m_wellPath = jobWellSettings->wellPath(); m_wellGroupName = jobWellSettings->wellGroupName(); m_openTimeStep = jobWellSettings->openTimeStep(); m_wellOpenType = jobWellSettings->wellOpenType(); m_wellOpenKeyword = jobWellSettings->wellOpenKeyword(); m_wconinjeKeyword = jobWellSettings->wconinjeKeyword(); m_wconprodKeyword = jobWellSettings->wconprodKeyword(); m_includeMSWData = jobWellSettings->includeMSWData(); updateConnectedEditors(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimOpmFlowJob::setAutoLoadResults( bool autoLoadResults ) { m_autoLoadResults = autoLoadResults; }