///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2017 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 "RicWellPathExportCompletionDataFeatureImpl.h" #include "RiaLogging.h" #include "RiaPreferencesSystem.h" #include "RiaQStringFormatter.h" #include "RiaWeightedMeanCalculator.h" #include "RigStatisticsTools.h" #include "RiuMessageDialog.h" #include "ExportCommands/RicExportLgrFeature.h" #include "RicExportCompletionDataSettingsUi.h" #include "RicExportFractureCompletionsImpl.h" #include "RicFishbonesTransmissibilityCalculationFeatureImp.h" #include "RicTransmissibilityCalculator.h" #include "RicWellPathExportCompletionsFileTools.h" #include "RicWellPathExportMswCompletionsImpl.h" #include "RicWellPathFractureReportItem.h" #include "RicWellPathFractureTextReportFeatureImpl.h" #include "RifTextDataTableFormatter.h" #include "RifThermalToStimPlanFractureXmlOutput.h" #include "RigActiveCellInfo.h" #include "RigEclipseCaseData.h" #include "RigMainGrid.h" #include "Well/RigWellLogExtractor.h" #include "Well/RigWellPath.h" #include "Well/RigWellPathIntersectionTools.h" #include "RicPerforationCellFilterEvaluator.h" #include "RimCellFilter.h" #include "RimEclipseCase.h" #include "RimFishbonesCollection.h" #include "RimNonDarcyPerforationParameters.h" #include "RimPerforationCollection.h" #include "RimPerforationInterval.h" #include "RimThermalFractureTemplate.h" #include "RimWellPath.h" #include "RimWellPathCompletions.h" #include "RimWellPathFracture.h" #include "RimWellPathFractureCollection.h" #include "Riu3DMainWindowTools.h" #include "cafProgressInfo.h" #include #include #include #include #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportCompletions( const std::vector& wellPaths, const RicExportCompletionDataSettingsUi& exportSettings ) { if ( exportSettings.caseToApply() == nullptr ) { RiaLogging::error( "Export Completions Data: Cannot export completions data a valid Eclipse case" ); return; } // Ensure that the case is open. This will enable export without any open views. // https://github.com/OPM/ResInsight/issues/11134 exportSettings.caseToApply()->ensureReservoirCaseIsOpen(); if ( exportSettings.caseToApply()->eclipseCaseData() == nullptr ) { RiaLogging::error( "Export Completions Data: No data available for Eclipse Case" ); return; } if ( exportSettings.customFileName().isEmpty() ) { QDir folder( exportSettings.folder ); if ( !folder.exists() ) { QString txt = QString( "The path '%1' does not exist. Aborting export." ).arg( exportSettings.folder ); RiuMessageDialog::showError( Riu3DMainWindowTools::mainWindowWidget(), "Export", txt ); return; } } exportCarfinForTemporaryLgrs( exportSettings.caseToApply(), exportSettings.folder ); { std::vector fractureDataReportItems; std::unique_ptr fractureTransmissibilityExportInformationStream = nullptr; QFile fractureTransmissibilityExportInformationFile; RiaPreferencesSystem* prefs = RiaPreferencesSystem::current(); if ( prefs->includeFractureDebugInfoFile() ) { QDir outputDir = QDir( exportSettings.folder ); if ( !outputDir.mkpath( "." ) ) { QString errMsg = QString( "Could not create export folder: %1" ).arg( exportSettings.folder ); RiaLogging::error( errMsg.toStdString() ); return; } QString fractureTransmisibillityExportInformationPath = QDir( exportSettings.folder ).absoluteFilePath( "FractureTransmissibilityExportInformation" ); fractureTransmissibilityExportInformationFile.setFileName( fractureTransmisibillityExportInformationPath ); if ( !fractureTransmissibilityExportInformationFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) { RiaLogging::error( std::format( "Export Completions Data: Could not open the file: {}", fractureTransmisibillityExportInformationPath ) ); } else { fractureTransmissibilityExportInformationStream = std::make_unique( &fractureTransmissibilityExportInformationFile ); } // Extra debugging for thermal fractures if ( exportSettings.includeFractures() ) { for ( RimWellPath* wellPath : wellPaths ) { std::vector allWellPathLaterals = wellPath->allWellPathLaterals(); for ( auto wellPathLateral : allWellPathLaterals ) { for ( auto& frac : wellPathLateral->fractureCollection()->activeFractures() ) { if ( RimThermalFractureTemplate* fractureTemplate = dynamic_cast( frac->fractureTemplate() ) ) { QString name = frac->name(); QString fractureXmlPath = QDir( exportSettings.folder ).absoluteFilePath( name + ".xml" ); RifThermalToStimPlanFractureXmlOutput::writeToFile( fractureTemplate, fractureXmlPath ); } } } } } } std::vector completions; { caf::ProgressInfo progress( wellPaths.size(), "Extracting Completion Data For Well Paths" ); for ( RimWellPath* wellPath : wellPaths ) { std::vector allWellPathLaterals; if ( wellPath->unitSystem() == exportSettings.caseToApply->eclipseCaseData()->unitsType() ) { allWellPathLaterals = wellPath->allWellPathLaterals(); } else { int caseId = exportSettings.caseToApply->caseId(); QString format = QString( "Unit systems for well path \"%1\" must match unit system of chosen eclipse case \"%2\"" ); QString errMsg = format.arg( wellPath->name() ).arg( caseId ); RiaLogging::error( errMsg.toStdString() ); } std::map> completionsPerEclipseCellAllCompletionTypes; for ( auto wellPathLateral : allWellPathLaterals ) { // Skip well paths that are not enabled, no export of WELSEGS or COMPDAT // https://github.com/OPM/ResInsight/issues/10754 // if ( !wellPathLateral->isEnabled() ) continue; // Generate completion data if ( exportSettings.includePerforations ) { auto exportDate = exportDateForTimeStep( exportSettings.caseToApply, exportSettings.timeStep ); std::vector perforationCompletionData = generatePerforationsCompdatValues( wellPathLateral, wellPathLateral->perforationIntervalCollection()->perforations(), exportSettings, exportDate ); appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, perforationCompletionData ); } if ( exportSettings.includeFishbones ) { // Make sure the start and end location is computed if needed wellPathLateral->fishbonesCollection()->computeStartAndEndLocation(); std::vector fishbonesCompletionData = RicFishbonesTransmissibilityCalculationFeatureImp::generateFishboneCompdatValuesUsingAdjustedCellVolume( wellPathLateral, exportSettings ); appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fishbonesCompletionData ); } if ( exportSettings.includeFractures() ) { // If no report is wanted, set reportItems = nullptr std::vector* reportItems = &fractureDataReportItems; std::vector fractureCompletionData = RicExportFractureCompletionsImpl:: generateCompdatValuesForWellPath( wellPathLateral, exportSettings.caseToApply(), reportItems, fractureTransmissibilityExportInformationStream.get(), RicExportFractureCompletionsImpl:: PressureDepletionParameters( exportSettings.performTransScaling(), exportSettings.transScalingTimeStep(), exportSettings.transScalingWBHPSource(), exportSettings.transScalingWBHP() ) ); appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fractureCompletionData ); } } for ( auto& data : completionsPerEclipseCellAllCompletionTypes ) { completions.push_back( combineEclipseCellCompletions( data.second, exportSettings ) ); } progress.incrementProgress(); } } // make sure we use per-connection D-factors, must be done after combining completions for ( auto& completion : completions ) { completion.setPerConnectionDfactor(); } const QString eclipseCaseName = exportSettings.caseToApply->caseUserDescription(); if ( exportSettings.fileSplit == RicExportCompletionDataSettingsUi::ExportSplit::UNIFIED_FILE ) { QString fileName = exportSettings.customFileName(); if ( fileName.isEmpty() ) { fileName = QString( "UnifiedCompletions_%1" ).arg( eclipseCaseName ); } sortAndExportCompletionsToFile( exportSettings.caseToApply, exportSettings.folder, fileName, completions, fractureDataReportItems, exportSettings.compdatExport, exportSettings.exportDataSourceAsComment(), exportSettings.exportWelspec() ); } else if ( exportSettings.fileSplit == RicExportCompletionDataSettingsUi::ExportSplit::SPLIT_ON_WELL ) { for ( auto wellPath : wellPaths ) { std::vector completionsForWell; for ( const auto& completion : completions ) { if ( wellPath == topLevelWellPath( completion ) ) { completionsForWell.push_back( completion ); } } if ( completionsForWell.empty() ) continue; std::vector reportItemsForWell; for ( const auto& fracItem : fractureDataReportItems ) { if ( fracItem.wellPathNameForExport() == wellPath->completionSettings()->wellNameForExport() ) { reportItemsForWell.push_back( fracItem ); } } QString fileName = QString( "%1_Completions_%2" ).arg( wellPath->name() ).arg( eclipseCaseName ); sortAndExportCompletionsToFile( exportSettings.caseToApply, exportSettings.folder, fileName, completionsForWell, reportItemsForWell, exportSettings.compdatExport, exportSettings.exportDataSourceAsComment(), exportSettings.exportWelspec() ); } } } if ( exportSettings.includeMsw ) { RicWellPathExportMswCompletionsImpl::exportWellSegmentsForAllCompletions( exportSettings, wellPaths ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportCompletionDataFeatureImpl::computeStaticCompletionsForWellPath( RimWellPath* wellPath, RimEclipseCase* eclipseCase ) { std::vector completionsPerEclipseCell; if ( eclipseCase && eclipseCase->eclipseCaseData() ) { RicExportCompletionDataSettingsUi exportSettings; exportSettings.caseToApply = eclipseCase; exportSettings.timeStep = 0; exportSettings.includeFishbones = true; exportSettings.includePerforations = true; exportSettings.includeFractures = true; { std::vector completionData = RicFishbonesTransmissibilityCalculationFeatureImp::generateFishboneCompdatValuesUsingAdjustedCellVolume( wellPath, exportSettings ); std::copy( completionData.begin(), completionData.end(), std::back_inserter( completionsPerEclipseCell ) ); } { std::vector completionData = RicExportFractureCompletionsImpl::generateCompdatValuesForWellPath( wellPath, eclipseCase, nullptr, nullptr ); std::copy( completionData.begin(), completionData.end(), std::back_inserter( completionsPerEclipseCell ) ); } } return completionsPerEclipseCell; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportCompletionDataFeatureImpl::computeDynamicCompletionsForWellPath( RimWellPath* wellPath, RimEclipseCase* eclipseCase, size_t timeStepIndex ) { CAF_ASSERT( eclipseCase ); std::vector completionsPerEclipseCell; if ( eclipseCase && eclipseCase->eclipseCaseData() ) { RicExportCompletionDataSettingsUi exportSettings; exportSettings.caseToApply = eclipseCase; exportSettings.timeStep = static_cast( timeStepIndex ); exportSettings.includeFishbones = true; exportSettings.includePerforations = true; exportSettings.includeFractures = true; auto exportDate = exportDateForTimeStep( eclipseCase, timeStepIndex ); completionsPerEclipseCell = generatePerforationsCompdatValues( wellPath, wellPath->perforationIntervalCollection()->perforations(), exportSettings, exportDate ); } return completionsPerEclipseCell; } //================================================================================================== /// //================================================================================================== RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCellCompletions( const std::vector& completions, const RicExportCompletionDataSettingsUi& settings ) { CAF_ASSERT( !completions.empty() ); // For detailed description of how the combined completion data is computed, see // https://github.com/OPM/ResInsight/issues/7049 const RigCompletionData& firstCompletion = completions[0]; const QString& wellName = firstCompletion.wellName(); const RigCompletionDataGridCell& cellIndexIJK = firstCompletion.completionDataGridCell(); RigCompletionData::CompletionType completionType = firstCompletion.completionType(); RigCompletionData resultCompletion( wellName, cellIndexIJK, firstCompletion.firstOrderingValue() ); resultCompletion.setSecondOrderingValue( firstCompletion.secondOrderingValue() ); resultCompletion.setSourcePdmObject( firstCompletion.sourcePdmObject() ); RigCompletionData::CellDirection cellDirection = firstCompletion.direction(); double largestTransmissibilityValue = firstCompletion.transmissibility(); RiaWeightedMeanCalculator diameterCalculator; RiaWeightedMeanCalculator skinFactorCalculator; auto isValidTransmissibility = []( double transmissibility ) { return RigStatisticsTools::isValidNumber( transmissibility ) && transmissibility >= 0.0; }; auto startMD = completions[0].startMD(); auto endMD = completions[0].endMD(); auto completionNumber = completions[0].completionNumber(); for ( const RigCompletionData& completion : completions ) { if ( !startMD.has_value() ) { startMD = completion.startMD(); } else { if ( completion.startMD().has_value() && ( completion.startMD().value() < startMD.value() ) ) { startMD = completion.startMD(); } } if ( !endMD.has_value() ) { endMD = completion.endMD(); } else { if ( completion.endMD().has_value() && ( completion.endMD().value() > endMD.value() ) ) { endMD = completion.endMD(); } } if ( !completionNumber.has_value() ) { if ( completion.completionNumber().has_value() ) { completionNumber = completion.completionNumber(); } } double transmissibility = completion.transmissibility(); if ( !isValidTransmissibility( transmissibility ) ) { QString errorMessage = QString( "Invalid or negative transmissibility value (%1) in cell %3" ) .arg( transmissibility ) .arg( cellIndexIJK.oneBasedLocalCellIndexString() ); RiaLogging::error( errorMessage.toStdString() ); resultCompletion.addMetadata( "ERROR", errorMessage ); continue; } diameterCalculator.addValueAndWeight( completion.diameter(), transmissibility ); skinFactorCalculator.addValueAndWeight( completion.skinFactor(), transmissibility ); if ( transmissibility > largestTransmissibilityValue ) { largestTransmissibilityValue = transmissibility; cellDirection = completion.direction(); } } if ( startMD.has_value() && endMD.has_value() ) { resultCompletion.setDepthRange( startMD.value(), endMD.value() ); } if ( completionNumber.has_value() ) resultCompletion.setCompletionNumber( completionNumber.value() ); double combinedDiameter = diameterCalculator.weightedMean(); double combinedSkinFactor = skinFactorCalculator.weightedMean(); double combinedTrans = 0.0; double combinedKh = 0.0; double combinedDFactor = 0.0; { RiaWeightedMeanCalculator dFactorCalculator; for ( const RigCompletionData& completion : completions ) { // Error is reported in the loop above, so we can skip this completion if ( !isValidTransmissibility( completion.transmissibility() ) ) continue; resultCompletion.m_metadata.reserve( resultCompletion.m_metadata.size() + completion.m_metadata.size() ); resultCompletion.m_metadata.insert( resultCompletion.m_metadata.end(), completion.m_metadata.begin(), completion.m_metadata.end() ); if ( completion.wellName() != firstCompletion.wellName() ) { QString errorMessage = QString( "Cannot combine completions of different types in same cell %1" ).arg( cellIndexIJK.oneBasedLocalCellIndexString() ); RiaLogging::error( errorMessage.toStdString() ); resultCompletion.addMetadata( "ERROR", errorMessage ); return resultCompletion; // Returning empty completion, should not be exported } if ( completion.transmissibility() == HUGE_VAL ) { QString errorMessage = QString( "Transmissibility calculation has failed for cell %1" ).arg( cellIndexIJK.oneBasedLocalCellIndexString() ); RiaLogging::error( errorMessage.toStdString() ); resultCompletion.addMetadata( "ERROR", errorMessage ); return resultCompletion; // Returning empty completion, should not be exported } combinedTrans = combinedTrans + completion.transmissibility(); combinedKh = combinedKh + completion.kh(); dFactorCalculator.addValueAndWeight( completion.dFactor(), completion.transmissibility() ); } // Arithmetic MEAN dFactor weighted by Tj/SumTj from the completions // Note : Divide by n is intentional, based on input from @hhgs in mail dated 18.01.2020 combinedDFactor = dFactorCalculator.weightedMean() / completions.size(); } if ( settings.compdatExport == RicExportCompletionDataSettingsUi::CompdatExport::TRANSMISSIBILITIES ) { resultCompletion.setCombinedValuesExplicitTrans( combinedTrans, combinedKh, combinedDFactor, combinedSkinFactor, combinedDiameter, cellDirection, completionType ); } else if ( settings.compdatExport == RicExportCompletionDataSettingsUi::CompdatExport::WPIMULT_AND_DEFAULT_CONNECTION_FACTORS ) { // calculate trans for main bore - but as Eclipse will do it! double transmissibilityEclipseCalculation = RicTransmissibilityCalculator::calculateTransmissibilityAsEclipseDoes( settings.caseToApply(), combinedSkinFactor, combinedDiameter / 2, cellIndexIJK.globalCellIndex(), cellDirection ); double defaultKh = RigCompletionData::defaultValue(); double wpimult = combinedTrans / transmissibilityEclipseCalculation; resultCompletion.setCombinedValuesImplicitTransWPImult( wpimult, defaultKh, combinedDFactor, combinedSkinFactor, combinedDiameter, cellDirection, completionType ); } return resultCompletion; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportCompletionDataFeatureImpl::mainGridCompletions( const std::vector& allCompletions ) { std::vector completions; for ( const auto& completion : allCompletions ) { QString gridName = completion.completionDataGridCell().lgrName(); if ( gridName.isEmpty() ) { completions.push_back( completion ); } } return completions; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::map> RicWellPathExportCompletionDataFeatureImpl::subGridsCompletions( const std::vector& allCompletions ) { std::map> completions; for ( const auto& completion : allCompletions ) { QString gridName = completion.completionDataGridCell().lgrName(); if ( !gridName.isEmpty() ) { auto it = completions.find( gridName ); if ( it == completions.end() ) { completions.insert( std::pair>( gridName, { completion } ) ); } else { it->second.push_back( completion ); } } } return completions; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportWellPathFractureReport( RimEclipseCase* sourceCase, QFilePtr exportFile, const std::vector& wellPathFractureReportItems ) { QTextStream stream( exportFile.get() ); if ( !wellPathFractureReportItems.empty() ) { std::vector sortedReportItems; { std::set fractureReportItemsSet; for ( const auto& reportItem : wellPathFractureReportItems ) { fractureReportItemsSet.insert( reportItem ); } for ( const auto& reportItem : fractureReportItemsSet ) { sortedReportItems.emplace_back( reportItem ); } } std::vector wellPathsToReport; { std::set wellPathsSet; auto allWellPaths = RicWellPathFractureTextReportFeatureImpl::wellPathsWithActiveFractures(); for ( const auto& wellPath : allWellPaths ) { for ( const auto& reportItem : sortedReportItems ) { if ( reportItem.wellPathNameForExport() == wellPath->completionSettings()->wellNameForExport() ) { wellPathsSet.insert( wellPath ); } } } std::copy( wellPathsSet.begin(), wellPathsSet.end(), std::back_inserter( wellPathsToReport ) ); } RicWellPathFractureTextReportFeatureImpl reportGenerator; QString summaryText = reportGenerator.wellPathFractureReport( sourceCase, wellPathsToReport, sortedReportItems ); stream << summaryText; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportWelspecsToFile( RimEclipseCase* gridCase, QFilePtr exportFile, const std::vector& completions, bool exportDataSourceAsComment ) { QTextStream stream( exportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setColumnSpacing( 2 ); formatter.setOptionalComment( exportDataSourceAsComment ); std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), // well RifTextDataTableColumn( "GROUP", "NAME" ), // group RifTextDataTableColumn( "", "I" ), // I RifTextDataTableColumn( "", "J" ), // J RifTextDataTableColumn( "BHP", "DEPTH" ), // RefDepth RifTextDataTableColumn( "PHASE", "FLUID" ), // Type RifTextDataTableColumn( "DRAIN", "AREA" ), // DrainRad RifTextDataTableColumn( "INFLOW", "EQUANS" ), // GasInEq RifTextDataTableColumn( "OPEN", "SHUT" ), // AutoShut RifTextDataTableColumn( "CROSS", "FLOW" ), // XFlow RifTextDataTableColumn( "PVT", "TABLE" ), // FluidPVT RifTextDataTableColumn( "HYDS", "DENS" ), // HydrDens RifTextDataTableColumn( "FIP", "REGN" ) // FluidInPla) }; }; formatter.header( header ); formatter.keyword( "WELSPECS" ); std::set wellPathSet; // Build list of unique RimWellPath for ( const auto& completion : completions ) { const auto wellPath = RicWellPathExportCompletionsFileTools::findWellPathFromExportName( completion.wellName() ); if ( wellPath ) { wellPathSet.insert( wellPath ); } } // Export for ( const auto wellPath : wellPathSet ) { auto completionSettings = wellPath->completionSettings(); auto ijIntersection = wellPathUpperGridIntersectionIJ( gridCase, wellPath ); formatter.add( completionSettings->wellNameForExport() ) .add( completionSettings->groupNameForExport() ) .add( ijIntersection.second.x() + 1 ) .add( ijIntersection.second.y() + 1 ) .add( completionSettings->referenceDepthForExport() ) .add( completionSettings->wellTypeNameForExport() ) .add( completionSettings->drainageRadiusForExport() ) .add( completionSettings->gasInflowEquationForExport() ) .add( completionSettings->automaticWellShutInForExport() ) .add( completionSettings->allowWellCrossFlowForExport() ) .add( completionSettings->wellBoreFluidPVTForExport() ) .add( completionSettings->hydrostaticDensityForExport() ) .add( completionSettings->fluidInPlaceRegionForExport() ) .rowCompleted(); } formatter.tableCompleted(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportWelspeclToFile( RimEclipseCase* gridCase, QFilePtr exportFile, const std::map>& completions ) { QTextStream stream( exportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setColumnSpacing( 2 ); std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), // well RifTextDataTableColumn( "GROUP", "NAME" ), // group RifTextDataTableColumn( "", "LGR" ), RifTextDataTableColumn( "", "I" ), // I RifTextDataTableColumn( "", "J" ), // J RifTextDataTableColumn( "BHP", "DEPTH" ), // RefDepth RifTextDataTableColumn( "PHASE", "FLUID" ), // Type RifTextDataTableColumn( "DRAIN", "AREA" ), // DrainRad RifTextDataTableColumn( "INFLOW", "EQUANS" ), // GasInEq RifTextDataTableColumn( "OPEN", "SHUT" ), // AutoShut RifTextDataTableColumn( "CROSS", "FLOW" ), // XFlow RifTextDataTableColumn( "PVT", "TABLE" ), // FluidPVT RifTextDataTableColumn( "HYDS", "DENS" ), // HydrDens RifTextDataTableColumn( "FIP", "REGN" ) // FluidInPla) }; }; formatter.header( header ); formatter.keyword( "WELSPECL" ); std::map> wellPathToLgrNameMap; for ( const auto& completionsForLgr : completions ) { for ( const auto& completion : completionsForLgr.second ) { const auto wellPath = RicWellPathExportCompletionsFileTools::findWellPathFromExportName( completion.wellName() ); if ( !wellPath ) continue; wellPathToLgrNameMap[wellPath].insert( completionsForLgr.first ); } } // See similar logic in RiaGrpcWellPathService::GetCompletionData() // A well must be defined only once. Use the LGR the well path enters first. for ( const auto& [wellPath, gridNames] : wellPathToLgrNameMap ) { QString firstLgrName; double firstMeasuredDepth = std::numeric_limits::max(); cvf::Vec2i firstIJ; bool isFound = false; for ( const auto& lgrName : gridNames ) { const auto& [measuredDepth, ijIntersection] = wellPathUpperGridIntersectionIJ( gridCase, wellPath, lgrName ); const bool isValid = measuredDepth != cvf::UNDEFINED_DOUBLE; if ( !isFound || ( isValid && measuredDepth < firstMeasuredDepth ) ) { firstLgrName = lgrName; firstMeasuredDepth = isValid ? measuredDepth : std::numeric_limits::max(); firstIJ = ijIntersection; isFound = true; } } if ( !isFound ) continue; auto completionSettings = wellPath->completionSettings(); formatter.add( completionSettings->wellNameForExport() ) .add( completionSettings->groupNameForExport() ) .add( firstLgrName ) .add( firstIJ.x() + 1 ) .add( firstIJ.y() + 1 ) .add( completionSettings->referenceDepthForExport() ) .add( completionSettings->wellTypeNameForExport() ) .add( completionSettings->drainageRadiusForExport() ) .add( completionSettings->gasInflowEquationForExport() ) .add( completionSettings->automaticWellShutInForExport() ) .add( completionSettings->allowWellCrossFlowForExport() ) .add( completionSettings->wellBoreFluidPVTForExport() ) .add( completionSettings->hydrostaticDensityForExport() ) .add( completionSettings->fluidInPlaceRegionForExport() ) .rowCompleted(); } formatter.tableCompleted(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::sortAndExportCompletionsToFile( RimEclipseCase* eclipseCase, const QString& folderName, const QString& fileName, const std::vector& completions, const std::vector& wellPathFractureReportItems, RicExportCompletionDataSettingsUi::CompdatExportType exportType, bool exportDataSourceAsComment, bool exportWelspec ) { // Sort completions based on grid they belong to std::vector completionsForMainGrid = mainGridCompletions( completions ); std::map> completionsForSubGrids = subGridsCompletions( completions ); if ( !completionsForMainGrid.empty() ) { QFileInfo fi( fileName ); std::shared_ptr exportFile = RicWellPathExportCompletionsFileTools::openFileForExport( folderName, fi.baseName(), fi.suffix(), exportDataSourceAsComment ); if ( exportFile ) { std::map> completionsForGrid; completionsForGrid.insert( std::pair>( "", completionsForMainGrid ) ); exportWellPathFractureReport( eclipseCase, exportFile, wellPathFractureReportItems ); if ( exportWelspec ) { exportWelspecsToFile( eclipseCase, exportFile, completionsForMainGrid, exportDataSourceAsComment ); } exportCompdatAndWpimultTables( eclipseCase, exportFile, completionsForGrid, exportType, exportDataSourceAsComment ); } } if ( !completionsForSubGrids.empty() ) { QFileInfo fi( fileName ); QString lgrFileName = fi.baseName() + "_LGR"; std::shared_ptr exportFile = RicWellPathExportCompletionsFileTools::openFileForExport( folderName, lgrFileName, fi.suffix(), exportDataSourceAsComment ); if ( exportFile ) { exportWellPathFractureReport( eclipseCase, exportFile, wellPathFractureReportItems ); if ( exportWelspec ) { exportWelspeclToFile( eclipseCase, exportFile, completionsForSubGrids ); } exportCompdatAndWpimultTables( eclipseCase, exportFile, completionsForSubGrids, exportType, exportDataSourceAsComment ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportCompdatAndWpimultTables( RimEclipseCase* sourceCase, QFilePtr exportFile, const std::map>& completionsPerGrid, RicExportCompletionDataSettingsUi::CompdatExportType exportType, bool exportDataSourceAsComment ) { if ( completionsPerGrid.empty() ) return; QTextStream stream( exportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setColumnSpacing( 3 ); formatter.setOptionalComment( exportDataSourceAsComment ); // All LGRs share one set of tables (COMPDATL, WPIMULTL, COMPLMPL) where each row carries its own LGR name std::map> completionsPerTable; for ( const auto& [gridName, gridCompletions] : completionsPerGrid ) { auto& tableCompletions = completionsPerTable[gridName.isEmpty() ? QString() : QString( "LGR" )]; tableCompletions.insert( tableCompletions.end(), gridCompletions.begin(), gridCompletions.end() ); } for ( const auto& gridCompletions : completionsPerTable ) { std::vector completions = gridCompletions.second; // Sort by well name / cell index std::sort( completions.begin(), completions.end() ); // Print completion data QString gridName = gridCompletions.first; exportCompdatTableUsingFormatter( formatter, gridName, completions ); if ( exportType == RicExportCompletionDataSettingsUi::CompdatExport::WPIMULT_AND_DEFAULT_CONNECTION_FACTORS ) { exportWpimultTableUsingFormatter( formatter, gridName, completions ); } for ( auto& completion : completions ) { if ( completion.completionNumber().has_value() ) { exportComplumpTableUsingFormatter( formatter, gridName, completions ); break; } } } RiaLogging::info( std::format( "Successfully exported completion data to {}", exportFile->fileName() ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportCompdatTableUsingFormatter( RifTextDataTableFormatter& formatter, const QString& gridName, const std::vector& completionData ) { if ( gridName.isEmpty() ) { std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), RifTextDataTableColumn( "", "I" ), RifTextDataTableColumn( "", "J" ), RifTextDataTableColumn( "", "K1" ), RifTextDataTableColumn( "", "K2" ), RifTextDataTableColumn( "OPEN", "SHUT" ), RifTextDataTableColumn( "SAT", "TAB" ), RifTextDataTableColumn( "CONN", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), RifTextDataTableColumn( "WELL", "DIA" ), RifTextDataTableColumn( "KH", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), RifTextDataTableColumn( "SKIN", "FACT" ), RifTextDataTableColumn( "D", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), RifTextDataTableColumn( "DIR", "PEN" ) }; formatter.header( header ); formatter.keyword( "COMPDAT" ); } else { std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), RifTextDataTableColumn( "LGR", "NAME" ), RifTextDataTableColumn( "", "I" ), RifTextDataTableColumn( "", "J" ), RifTextDataTableColumn( "", "K1" ), RifTextDataTableColumn( "", "K2" ), RifTextDataTableColumn( "OPEN", "SHUT" ), RifTextDataTableColumn( "SAT", "TAB" ), RifTextDataTableColumn( "CONN", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), RifTextDataTableColumn( "WELL", "DIA" ), RifTextDataTableColumn( "KH", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), RifTextDataTableColumn( "SKIN", "FACT" ), RifTextDataTableColumn( "D", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), RifTextDataTableColumn( "DIR", "PEN" ) }; formatter.header( header ); formatter.keyword( "COMPDATL" ); } RigCompletionData::CompletionType currentCompletionType = RigCompletionData::CompletionType::CT_UNDEFINED; for ( const RigCompletionData& data : completionData ) { if ( currentCompletionType != data.completionType() ) { // The completions are sorted by completion type, write out a heading when completion type changes QString txt; if ( data.completionType() == RigCompletionData::CompletionType::FISHBONES ) txt = "Fishbones"; if ( data.completionType() == RigCompletionData::CompletionType::FRACTURE ) txt = "Fracture"; if ( data.completionType() == RigCompletionData::CompletionType::PERFORATION ) txt = "Perforation"; formatter.addOptionalComment( "---- Completions for completion type " + txt + " ----" ); currentCompletionType = data.completionType(); } for ( const RigCompletionMetaData& metadata : data.metadata() ) { formatter.addOptionalComment( QString( "%1 : %2" ).arg( metadata.name ).arg( metadata.comment ) ); } if ( data.transmissibility() == 0.0 || data.wpimult() == 0.0 ) { // Don't export completions without transmissibility continue; } formatter.add( data.wellName() ); if ( !gridName.isEmpty() ) { formatter.add( data.completionDataGridCell().lgrName() ); } formatter.add( data.completionDataGridCell().localCellIndexI() + 1 ) .add( data.completionDataGridCell().localCellIndexJ() + 1 ) .add( data.completionDataGridCell().localCellIndexK() + 1 ) .add( data.completionDataGridCell().localCellIndexK() + 1 ); formatter.add( "OPEN" ); formatter.addValueOrDefaultMarker( data.saturation(), RigCompletionData::defaultValue() ); formatter.addValueOrDefaultMarker( data.transmissibility(), RigCompletionData::defaultValue() ); formatter.addValueOrDefaultMarker( data.diameter(), RigCompletionData::defaultValue() ); formatter.addValueOrDefaultMarker( data.kh(), RigCompletionData::defaultValue() ); formatter.addValueOrDefaultMarker( data.skinFactor(), RigCompletionData::defaultValue() ); formatter.addValueOrDefaultMarker( data.dFactor(), RigCompletionData::defaultValue() ); switch ( data.direction() ) { case RigCompletionData::CellDirection::DIR_I: formatter.add( "'X'" ); break; case RigCompletionData::CellDirection::DIR_J: formatter.add( "'Y'" ); break; case RigCompletionData::CellDirection::DIR_K: default: formatter.add( "'Z'" ); break; } formatter.rowCompleted(); } formatter.tableCompleted(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter, const QString& gridName, const std::vector& completionData ) { if ( gridName.isEmpty() ) { std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), RifTextDataTableColumn( "", "I" ), RifTextDataTableColumn( "", "J" ), RifTextDataTableColumn( "", "K1" ), RifTextDataTableColumn( "", "K2" ), RifTextDataTableColumn( "COMP", "NUM" ) }; formatter.header( header ); formatter.keyword( "COMPLUMP" ); } else { std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), RifTextDataTableColumn( "LGR", "NAME" ), RifTextDataTableColumn( "", "I" ), RifTextDataTableColumn( "", "J" ), RifTextDataTableColumn( "", "K1" ), RifTextDataTableColumn( "", "K2" ), RifTextDataTableColumn( "COMP", "NUM" ) }; formatter.header( header ); formatter.keyword( "COMPLMPL" ); } for ( const RigCompletionData& data : completionData ) { if ( !data.completionNumber().has_value() || data.transmissibility() == 0.0 || data.wpimult() == 0.0 ) { // Don't export completions without transmissibility or completion number set continue; } formatter.add( data.wellName() ); if ( !gridName.isEmpty() ) { formatter.add( data.completionDataGridCell().lgrName() ); } formatter.add( data.completionDataGridCell().localCellIndexI() + 1 ) .add( data.completionDataGridCell().localCellIndexJ() + 1 ) .add( data.completionDataGridCell().localCellIndexK() + 1 ) .add( data.completionDataGridCell().localCellIndexK() + 1 ) .add( data.completionNumber().value() ); formatter.rowCompleted(); } formatter.tableCompleted(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportWpimultTableUsingFormatter( RifTextDataTableFormatter& formatter, const QString& gridName, const std::vector& completionData ) { std::vector header; if ( gridName.isEmpty() ) { header = { RifTextDataTableColumn( "Well" ), RifTextDataTableColumn( "Mult" ), RifTextDataTableColumn( "I" ), RifTextDataTableColumn( "J" ), RifTextDataTableColumn( "K" ), }; formatter.keyword( "WPIMULT" ); } else { header = { RifTextDataTableColumn( "Well" ), RifTextDataTableColumn( "LgrName" ), RifTextDataTableColumn( "Mult" ), RifTextDataTableColumn( "I" ), RifTextDataTableColumn( "J" ), RifTextDataTableColumn( "K" ), }; formatter.keyword( "WPIMULTL" ); } formatter.header( header ); for ( auto& completion : completionData ) { if ( completion.wpimult() == 0.0 || RigCompletionData::isDefaultValue( completion.wpimult() ) ) { continue; } formatter.add( completion.wellName() ); if ( !gridName.isEmpty() ) { formatter.add( completion.completionDataGridCell().lgrName() ); } formatter.add( completion.wpimult() ); formatter.add( completion.completionDataGridCell().localCellIndexI() + 1 ) .add( completion.completionDataGridCell().localCellIndexJ() + 1 ) .add( completion.completionDataGridCell().localCellIndexK() + 1 ); formatter.rowCompleted(); } formatter.tableCompleted(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportCompletionDataFeatureImpl::generatePerforationsCompdatValues( const RimWellPath* wellPath, const std::vector& intervals, const RicExportCompletionDataSettingsUi& settings, const std::optional& exportDate ) { RiaDefines::EclipseUnitSystem unitSystem = settings.caseToApply->eclipseCaseData()->unitsType(); std::vector completionData; const RigActiveCellInfo* activeCellInfo = settings.caseToApply->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); auto wellPathGeometry = wellPath->wellPathGeometry(); if ( !wellPathGeometry ) return completionData; const RimNonDarcyPerforationParameters* nonDarcyParameters = wellPath->perforationIntervalCollection()->nonDarcyParameters(); if ( wellPath->perforationIntervalCollection()->isChecked() ) { for ( const RimPerforationInterval* interval : intervals ) { if ( !interval->isChecked() ) continue; if ( exportDate.has_value() && !interval->isActiveOnDate( *exportDate ) ) continue; std::pair, std::vector> perforationPointsAndMD = wellPathGeometry->clippedPointSubset( interval->startMD(), interval->endMD() ); std::vector intersectedCells = RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( settings.caseToApply->eclipseCaseData(), wellPath->name(), perforationPointsAndMD.first, perforationPointsAndMD.second ); RicPerforationCellFilterEvaluator filterEval( interval->cellFilter(), settings.caseToApply ); const size_t completionCountBefore = completionData.size(); for ( auto& cell : intersectedCells ) { bool cellIsActive = activeCellInfo->isActive( ReservoirCellIndex( cell.globCellIndex ) ); if ( !cellIsActive ) continue; if ( !filterEval.includesGlobalCell( cell.globCellIndex ) ) continue; RigCompletionData completion( wellPath->completionSettings()->wellNameForExport(), RigCompletionDataGridCell( cell.globCellIndex, settings.caseToApply->mainGrid() ), cell.startMD ); RigCompletionData::CellDirection direction = RicTransmissibilityCalculator::calculateCellMainDirection( settings.caseToApply, cell.globCellIndex, cell.intersectionLengthsInCellCS ); double transmissibility = 0.0; double kh = RigCompletionData::defaultValue(); double dFactor = RigCompletionData::defaultValue(); { auto transmissibilityData = RicTransmissibilityCalculator::calculateTransmissibilityData( settings.caseToApply, wellPath, cell.intersectionLengthsInCellCS, interval->skinFactor(), interval->diameter( unitSystem ) / 2, cell.globCellIndex, settings.useLateralNTG ); transmissibility = transmissibilityData.connectionFactor(); kh = transmissibilityData.kh(); if ( nonDarcyParameters->nonDarcyFlowType() == RimNonDarcyPerforationParameters::NON_DARCY_COMPUTED ) { const double effectiveH = transmissibilityData.effectiveH(); const double effectivePermeability = nonDarcyParameters->gridPermeabilityScalingFactor() * transmissibilityData.effectiveK(); dFactor = RicTransmissibilityCalculator::calculateDFactor( settings.caseToApply, effectiveH, cell.globCellIndex, wellPath->perforationIntervalCollection()->nonDarcyParameters(), effectivePermeability ); } } completion.setTransAndWPImultBackgroundDataFromPerforation( transmissibility, interval->skinFactor(), interval->diameter( unitSystem ), dFactor, kh, direction ); completion.setDepthRange( cell.startMD, cell.endMD ); if ( interval->completionNumber() > 0 ) completion.setCompletionNumber( interval->completionNumber() ); completion.addMetadata( "Perforation Completion", QString( "MD In: %1 - MD Out: %2" ).arg( cell.startMD ).arg( cell.endMD ) + QString( " Transmissibility: " ) + QString::number( transmissibility ) ); completion.setSourcePdmObject( interval ); completionData.push_back( completion ); } if ( filterEval.isEnabled() && completionData.size() == completionCountBefore && filterEval.rejectedCellCount() > 0 ) { RiaLogging::info( std::format( "Perforation '{}' on well '{}': all {} intersected cells suppressed by attached cell filter '{}'.", interval->name(), wellPath->name(), filterEval.rejectedCellCount(), interval->cellFilter() ? interval->cellFilter()->fullName() : QString() ) ); } } } if ( nonDarcyParameters->nonDarcyFlowType() == RimNonDarcyPerforationParameters::NON_DARCY_USER_DEFINED ) { // Compute D-factor for completion as // D_cell = D_well * Sum_Tran_cells / Tran_cell // // See https://github.com/OPM/ResInsight/issues/7450 double accumulatedTransmissibility = 0.0; for ( const auto& completion : completionData ) { accumulatedTransmissibility += completion.transmissibility(); } double userDefinedDFactor = nonDarcyParameters->userDefinedDFactor(); for ( auto& completion : completionData ) { double dFactorForCompletion = userDefinedDFactor * accumulatedTransmissibility / completion.transmissibility(); completion.setDFactor( dFactorForCompletion ); } } return completionData; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::appendCompletionData( std::map>* completionData, const std::vector& completionsToAppend ) { for ( const auto& completion : completionsToAppend ) { auto it = completionData->find( completion.completionDataGridCell().globalCellIndex() ); if ( it != completionData->end() ) { it->second.push_back( completion ); } else { completionData->insert( std::pair>( completion.completionDataGridCell().globalCellIndex(), std::vector{ completion } ) ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( const RimEclipseCase* gridCase, const RimWellPath* wellPath, const QString& gridName ) { const RigEclipseCaseData* caseData = gridCase->eclipseCaseData(); const RigMainGrid* mainGrid = caseData->mainGrid(); const RigActiveCellInfo* activeCellInfo = caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); auto wellPathGeometry = wellPath->wellPathGeometry(); if ( !wellPathGeometry ) return std::make_pair( cvf::UNDEFINED_DOUBLE, cvf::Vec2i() ); const std::vector& coords = wellPathGeometry->wellPathPoints(); const std::vector& mds = wellPathGeometry->measuredDepths(); if ( coords.empty() || mds.empty() ) return std::make_pair( cvf::UNDEFINED_DOUBLE, cvf::Vec2i() ); std::vector intersections = RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( caseData, wellPath->name(), coords, mds ); int gridId = 0; if ( !gridName.isEmpty() ) { const auto grid = caseData->grid( gridName ); if ( grid ) gridId = grid->gridId(); } for ( const WellPathCellIntersectionInfo& intersection : intersections ) { size_t gridLocalCellIndex = 0; const RigGridBase* grid = mainGrid->gridAndGridLocalIdxFromGlobalCellIdx( intersection.globCellIndex, &gridLocalCellIndex ); if ( grid->gridId() == gridId && activeCellInfo->isActive( ReservoirCellIndex( intersection.globCellIndex ) ) ) { size_t i, j, k; if ( grid->ijkFromCellIndex( gridLocalCellIndex, &i, &j, &k ) ) { return std::make_pair( intersection.startMD, cvf::Vec2i( (int)i, (int)j ) ); } } } return std::make_pair( cvf::UNDEFINED_DOUBLE, cvf::Vec2i() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimWellPath* RicWellPathExportCompletionDataFeatureImpl::topLevelWellPath( const RigCompletionData& completion ) { if ( completion.sourcePdmObject() ) { auto parentWellPath = completion.sourcePdmObject()->firstAncestorOrThisOfType(); if ( parentWellPath ) { return parentWellPath->topLevelWellPath(); } } return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportCompletionDataFeatureImpl::exportCarfinForTemporaryLgrs( const RimEclipseCase* sourceCase, const QString& folder ) { if ( !sourceCase || !sourceCase->mainGrid() ) return; const auto mainGrid = sourceCase->mainGrid(); const auto& lgrInfosForWells = RicExportLgrFeature::createLgrInfoListForTemporaryLgrs( mainGrid ); for ( const auto& lgrInfoForWell : lgrInfosForWells ) { RicExportLgrFeature::exportLgrs( folder, lgrInfoForWell.first, lgrInfoForWell.second ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( RimWellPath* wellPath, RimEclipseCase* eCase, const std::optional& exportDate ) { if ( wellPath == nullptr ) return {}; return completionDataForWellPaths( { wellPath }, eCase, exportDate ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPaths( const std::vector& wellPaths, RimEclipseCase* eCase, const std::optional& exportDate ) { if ( eCase == nullptr ) return {}; // Ensure that the case is open. This will enable export without any open views. eCase->ensureReservoirCaseIsOpen(); if ( eCase->eclipseCaseData() == nullptr ) { RiaLogging::error( "Export Completions Data: No data available for Eclipse Case" ); return {}; } RicExportCompletionDataSettingsUi exportSettings; exportSettings.caseToApply = eCase; exportSettings.includeMsw = false; exportSettings.setExportDataSourceAsComment( true ); exportSettings.includePerforations = true; exportSettings.includeFishbones = true; exportSettings.includeFractures = true; std::vector completions; // Completions from all given well paths (and their laterals) are combined per cell, so that cells shared // by several well paths are exported as a single connection. std::vector allWellPathLaterals; for ( auto* wellPath : wellPaths ) { if ( !wellPath || wellPath->unitSystem() != eCase->eclipseCaseData()->unitsType() ) continue; for ( auto* lateral : wellPath->allWellPathLaterals() ) { if ( std::find( allWellPathLaterals.begin(), allWellPathLaterals.end(), lateral ) == allWellPathLaterals.end() ) { allWellPathLaterals.push_back( lateral ); } } } std::map> completionsPerEclipseCellAllCompletionTypes; for ( auto wellPathLateral : allWellPathLaterals ) { // Skip well paths that are not enabled, no export of WELSEGS or COMPDAT // https://github.com/OPM/ResInsight/issues/10754 // if ( !wellPathLateral->isEnabled() ) continue; // Generate completion data if ( exportSettings.includePerforations ) { std::vector perforationCompletionData = generatePerforationsCompdatValues( wellPathLateral, wellPathLateral->perforationIntervalCollection()->perforations(), exportSettings, exportDate ); appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, perforationCompletionData ); } if ( exportSettings.includeFishbones ) { // Make sure the start and end location is computed if needed wellPathLateral->fishbonesCollection()->computeStartAndEndLocation(); std::vector fishbonesCompletionData = RicFishbonesTransmissibilityCalculationFeatureImp::generateFishboneCompdatValuesUsingAdjustedCellVolume( wellPathLateral, exportSettings ); appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fishbonesCompletionData ); } if ( exportSettings.includeFractures() ) { // If no report is wanted, set reportItems = nullptr std::vector* reportItems = nullptr; std::vector fractureCompletionData = RicExportFractureCompletionsImpl:: generateCompdatValuesForWellPath( wellPathLateral, exportSettings.caseToApply(), reportItems, nullptr, RicExportFractureCompletionsImpl:: PressureDepletionParameters( exportSettings.performTransScaling(), exportSettings.transScalingTimeStep(), exportSettings.transScalingWBHPSource(), exportSettings.transScalingWBHP() ) ); appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fractureCompletionData ); } } for ( auto& data : completionsPerEclipseCellAllCompletionTypes ) { completions.push_back( combineEclipseCellCompletions( data.second, exportSettings ) ); } std::sort( completions.begin(), completions.end() ); // make sure we set per-connection D-factors, must be done after combining completions for ( auto& completion : completions ) { completion.setPerConnectionDfactor(); // we calculate per connection D-factors, which are negative per definition (ref. OPM Flow // manual) } return completions; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::optional RicWellPathExportCompletionDataFeatureImpl::exportDateForTimeStep( const RimEclipseCase* eclipseCase, size_t timeStepIndex ) { if ( !eclipseCase ) return std::nullopt; auto timeSteps = eclipseCase->timeStepDates(); if ( timeStepIndex < timeSteps.size() ) return timeSteps[timeStepIndex]; return std::nullopt; }