///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2018 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 "RicWellPathExportMswCompletionsImpl.h" #include "RiaLogging.h" #include "RiaWeightedMeanCalculator.h" #include "RicExportCompletionDataSettingsUi.h" #include "RicExportFractureCompletionsImpl.h" #include "RicMswExportInfo.h" #include "RicMswTableFormatterTools.h" #include "RicMswValveAccumulators.h" #include "RicWellPathExportCompletionsFileTools.h" #include "RifTextDataTableFormatter.h" #include "RigActiveCellInfo.h" #include "RigEclipseCaseData.h" #include "RigGridBase.h" #include "RigMainGrid.h" #include "RigWellLogExtractor.h" #include "RigWellPath.h" #include "RigWellPathIntersectionTools.h" #include "RimEclipseCase.h" #include "RimFishbones.h" #include "RimFishbonesCollection.h" #include "RimFractureTemplate.h" #include "RimModeledWellPath.h" #include "RimMswCompletionParameters.h" #include "RimPerforationCollection.h" #include "RimPerforationInterval.h" #include "RimProject.h" #include "RimWellPath.h" #include "RimWellPathCompletions.h" #include "RimWellPathFracture.h" #include "RimWellPathFractureCollection.h" #include "RimWellPathTieIn.h" #include "RimWellPathValve.h" #include #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::exportWellSegmentsForAllCompletions( const RicExportCompletionDataSettingsUi& exportSettings, const std::vector& wellPaths ) { std::shared_ptr unifiedExportFile; std::shared_ptr unifiedLgrExportFile; if ( exportSettings.fileSplit() == RicExportCompletionDataSettingsUi::ExportSplit::UNIFIED_FILE ) { { QString fileName; QString folderName; QFileInfo fi( exportSettings.customFileName() ); if ( !exportSettings.customFileName().isEmpty() ) { fileName = fi.baseName() + "_MSW"; folderName = fi.absolutePath(); } else { fileName = QString( "UnifiedCompletions_MSW_%1" ).arg( exportSettings.caseToApply->caseUserDescription() ); folderName = exportSettings.folder; } unifiedExportFile = RicWellPathExportCompletionsFileTools::openFileForExport( folderName, fileName, fi.suffix(), exportSettings.exportDataSourceAsComment() ); } { QString lgrFileName; QString folderName; QFileInfo fi( exportSettings.customFileName() ); if ( !exportSettings.customFileName().isEmpty() ) { lgrFileName = fi.baseName() + "_LGR_MSW"; folderName = fi.absolutePath(); } else { lgrFileName = QString( "UnifiedCompletions_LGR_MSW_%1" ).arg( exportSettings.caseToApply->caseUserDescription() ); folderName = exportSettings.folder; } unifiedLgrExportFile = RicWellPathExportCompletionsFileTools::openFileForExport( folderName, lgrFileName, fi.suffix(), exportSettings.exportDataSourceAsComment() ); } } for ( const auto& wellPath : wellPaths ) { std::shared_ptr unifiedWellPathFile; std::shared_ptr unifiedLgrWellPathFile; auto allCompletions = wellPath->allCompletionsRecursively(); bool exportFractures = exportSettings.includeFractures() && std::any_of( allCompletions.begin(), allCompletions.end(), []( auto completion ) { return completion->isEnabled() && completion->componentType() == RiaDefines::WellPathComponentType::FRACTURE; } ); bool exportFishbones = exportSettings.includeFishbones() && std::any_of( allCompletions.begin(), allCompletions.end(), []( auto completion ) { return completion->isEnabled() && completion->componentType() == RiaDefines::WellPathComponentType::FISHBONES; } ); if ( exportSettings.fileSplit() == RicExportCompletionDataSettingsUi::ExportSplit::SPLIT_ON_WELL && !unifiedWellPathFile ) { QString wellFileName = QString( "%1_UnifiedCompletions_MSW_%2" ) .arg( wellPath->name(), exportSettings.caseToApply->caseUserDescription() ); unifiedWellPathFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, wellFileName, "", exportSettings.exportDataSourceAsComment() ); unifiedLgrWellPathFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, wellFileName + "_LGR", "", exportSettings.exportDataSourceAsComment() ); } { // Always use perforation functions to export well segments along well path. // If no perforations are present, skip Perforation from file name std::shared_ptr exportFile; std::shared_ptr lgrExportFile; if ( unifiedExportFile ) { exportFile = unifiedExportFile; lgrExportFile = unifiedLgrExportFile; } else if ( unifiedWellPathFile ) { exportFile = unifiedWellPathFile; lgrExportFile = unifiedLgrWellPathFile; } else { bool anyPerforationsPresent = exportSettings.includeFractures() && std::any_of( allCompletions.begin(), allCompletions.end(), []( auto completion ) { return completion->isEnabled() && completion->componentType() == RiaDefines::WellPathComponentType::PERFORATION_INTERVAL; } ); QString perforationText = anyPerforationsPresent ? "Perforation_" : ""; QString fileName = QString( "%1_%2MSW_%3" ) .arg( wellPath->name(), perforationText, exportSettings.caseToApply->caseUserDescription() ); exportFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, fileName, "", exportSettings.exportDataSourceAsComment() ); lgrExportFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, fileName + "_LGR", "", exportSettings.exportDataSourceAsComment() ); } exportWellSegmentsForPerforations( exportSettings.caseToApply, exportFile, lgrExportFile, wellPath, exportSettings.timeStep, exportSettings.exportDataSourceAsComment(), exportSettings.exportCompletionWelspecAfterMainBore() ); } if ( exportFractures ) { std::shared_ptr exportFile; std::shared_ptr lgrExportFile; if ( unifiedExportFile ) { exportFile = unifiedExportFile; lgrExportFile = unifiedLgrExportFile; } else if ( unifiedWellPathFile ) { exportFile = unifiedWellPathFile; lgrExportFile = unifiedLgrWellPathFile; } else { QString fileName = QString( "%1_Fracture_MSW_%2" ).arg( wellPath->name(), exportSettings.caseToApply->caseUserDescription() ); exportFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, fileName, "", exportSettings.exportDataSourceAsComment() ); lgrExportFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, fileName + "_LGR", "", exportSettings.exportDataSourceAsComment() ); } exportWellSegmentsForFractures( exportSettings.caseToApply, exportFile, lgrExportFile, wellPath, exportSettings.exportDataSourceAsComment(), exportSettings.exportCompletionWelspecAfterMainBore() ); } if ( exportFishbones ) { std::shared_ptr exportFile; std::shared_ptr lgrExportFile; if ( unifiedExportFile ) { exportFile = unifiedExportFile; lgrExportFile = unifiedLgrExportFile; } else if ( unifiedWellPathFile ) { exportFile = unifiedWellPathFile; lgrExportFile = unifiedLgrWellPathFile; } else { QString fileName = QString( "%1_Fishbones_MSW_%2" ).arg( wellPath->name(), exportSettings.caseToApply->caseUserDescription() ); exportFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, fileName, "", exportSettings.exportDataSourceAsComment() ); lgrExportFile = RicWellPathExportCompletionsFileTools::openFileForExport( exportSettings.folder, fileName + "_LGR", "", exportSettings.exportDataSourceAsComment() ); } exportWellSegmentsForFishbones( exportSettings.caseToApply, exportFile, lgrExportFile, wellPath, exportSettings.exportDataSourceAsComment(), exportSettings.exportCompletionWelspecAfterMainBore() ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::exportWellSegmentsForPerforations( RimEclipseCase* eclipseCase, std::shared_ptr exportFile, std::shared_ptr lgrExportFile, const RimWellPath* wellPath, int timeStep, bool exportDataSourceAsComment, bool completionSegmentsAfterMainBore ) { RiaDefines::EclipseUnitSystem unitSystem = eclipseCase->eclipseCaseData()->unitsType(); auto mswParameters = wellPath->mswCompletionParameters(); if ( !mswParameters ) return; auto cellIntersections = generateCellSegments( eclipseCase, wellPath ); double initialMD = computeIntitialMeasuredDepth( eclipseCase, wellPath, mswParameters, cellIntersections ); RicMswExportInfo exportInfo( wellPath, unitSystem, initialMD, mswParameters->lengthAndDepth().text(), mswParameters->pressureDrop().text() ); if ( generatePerforationsMswExportInfo( eclipseCase, wellPath, timeStep, initialMD, cellIntersections, &exportInfo, exportInfo.mainBoreBranch() ) ) { int branchNumber = 1; assignBranchNumbersToBranch( eclipseCase, &exportInfo, exportInfo.mainBoreBranch(), &branchNumber ); double maxSegmentLength = mswParameters->maxSegmentLength(); { QTextStream stream( exportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setOptionalComment( exportDataSourceAsComment ); RicMswTableFormatterTools::generateWelsegsTable( formatter, exportInfo, maxSegmentLength, completionSegmentsAfterMainBore ); bool exportLgrData = false; RicMswTableFormatterTools::generateCompsegTables( formatter, exportInfo, exportLgrData ); RicMswTableFormatterTools::generateWsegvalvTable( formatter, exportInfo ); RicMswTableFormatterTools::generateWsegAicdTable( formatter, exportInfo ); } if ( exportInfo.hasSubGridIntersections() ) { QTextStream stream( lgrExportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setOptionalComment( exportDataSourceAsComment ); RicMswTableFormatterTools::generateWelsegsTable( formatter, exportInfo, maxSegmentLength, completionSegmentsAfterMainBore ); bool exportLgrData = true; RicMswTableFormatterTools::generateCompsegTables( formatter, exportInfo, exportLgrData ); RicMswTableFormatterTools::generateWsegvalvTable( formatter, exportInfo ); RicMswTableFormatterTools::generateWsegAicdTable( formatter, exportInfo ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::exportWellSegmentsForFractures( RimEclipseCase* eclipseCase, std::shared_ptr exportFile, std::shared_ptr lgrExportFile, const RimWellPath* wellPath, bool exportDataSourceAsComment, bool completionSegmentsAfterMainBore ) { RiaDefines::EclipseUnitSystem unitSystem = eclipseCase->eclipseCaseData()->unitsType(); if ( eclipseCase == nullptr ) { RiaLogging::error( "Export Fracture Well Segments: Cannot export completions data without specified eclipse case" ); return; } auto mswParameters = wellPath->mswCompletionParameters(); if ( !mswParameters ) return; auto cellIntersections = generateCellSegments( eclipseCase, wellPath ); double initialMD = computeIntitialMeasuredDepth( eclipseCase, wellPath, mswParameters, cellIntersections ); RicMswExportInfo exportInfo( wellPath, unitSystem, initialMD, mswParameters->lengthAndDepth().text(), mswParameters->pressureDrop().text() ); generateFracturesMswExportInfo( eclipseCase, wellPath, initialMD, cellIntersections, &exportInfo, exportInfo.mainBoreBranch() ); int branchNumber = 1; assignBranchNumbersToBranch( eclipseCase, &exportInfo, exportInfo.mainBoreBranch(), &branchNumber ); auto doExport = []( RifTextDataTableFormatter& formatter, bool exportDataSourceAsComment, RicMswExportInfo& exportInfo, const RimWellPath* wellPath, bool completionSegmentsAfterMainBore, bool exportLgrData ) { formatter.setOptionalComment( exportDataSourceAsComment ); double maxSegmentLength = wellPath->mswCompletionParameters()->maxSegmentLength(); RicMswTableFormatterTools::generateWelsegsTable( formatter, exportInfo, maxSegmentLength, completionSegmentsAfterMainBore ); RicMswTableFormatterTools::generateCompsegTables( formatter, exportInfo, exportLgrData ); }; { QTextStream stream( exportFile.get() ); RifTextDataTableFormatter formatter( stream ); bool exportLgrData = false; doExport( formatter, exportDataSourceAsComment, exportInfo, wellPath, completionSegmentsAfterMainBore, exportLgrData ); } if ( exportInfo.hasSubGridIntersections() ) { QTextStream stream( lgrExportFile.get() ); RifTextDataTableFormatter formatter( stream ); bool exportLgrData = true; doExport( formatter, exportDataSourceAsComment, exportInfo, wellPath, completionSegmentsAfterMainBore, exportLgrData ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::exportWellSegmentsForFishbones( RimEclipseCase* eclipseCase, std::shared_ptr exportFile, std::shared_ptr lgrExportFile, const RimWellPath* wellPath, bool exportDataSourceAsComment, bool completionSegmentsAfterMainBore ) { auto fishbonesSubs = wellPath->fishbonesCollection()->activeFishbonesSubs(); if ( eclipseCase == nullptr ) { RiaLogging::error( "Export Well Segments: Cannot export completions data without specified eclipse case" ); return; } auto mswParameters = wellPath->mswCompletionParameters(); auto cellIntersections = generateCellSegments( eclipseCase, wellPath ); double initialMD = computeIntitialMeasuredDepth( eclipseCase, wellPath, mswParameters, cellIntersections ); RiaDefines::EclipseUnitSystem unitSystem = eclipseCase->eclipseCaseData()->unitsType(); RicMswExportInfo exportInfo( wellPath, unitSystem, initialMD, mswParameters->lengthAndDepth().text(), mswParameters->pressureDrop().text() ); generateFishbonesMswExportInfo( eclipseCase, wellPath, initialMD, cellIntersections, fishbonesSubs, true, &exportInfo, exportInfo.mainBoreBranch() ); updateDataForMultipleItemsInSameGridCell( exportInfo.mainBoreBranch() ); int branchNumber = 1; assignBranchNumbersToBranch( eclipseCase, &exportInfo, exportInfo.mainBoreBranch(), &branchNumber ); double maxSegmentLength = wellPath->mswCompletionParameters()->maxSegmentLength(); { QTextStream stream( exportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setOptionalComment( exportDataSourceAsComment ); RicMswTableFormatterTools::generateWelsegsTable( formatter, exportInfo, maxSegmentLength, completionSegmentsAfterMainBore ); bool exportLgrData = false; RicMswTableFormatterTools::generateCompsegTables( formatter, exportInfo, exportLgrData ); RicMswTableFormatterTools::generateWsegvalvTable( formatter, exportInfo ); } if ( exportInfo.hasSubGridIntersections() ) { QTextStream stream( lgrExportFile.get() ); RifTextDataTableFormatter formatter( stream ); formatter.setOptionalComment( exportDataSourceAsComment ); RicMswTableFormatterTools::generateWelsegsTable( formatter, exportInfo, maxSegmentLength, completionSegmentsAfterMainBore ); bool exportLgr = true; RicMswTableFormatterTools::generateCompsegTables( formatter, exportInfo, exportLgr ); RicMswTableFormatterTools::generateWsegvalvTable( formatter, exportInfo ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::updateDataForMultipleItemsInSameGridCell( gsl::not_null branch ) { auto allSegments = branch->allSegmentsRecursively(); { // Update effective diameter // https://github.com/OPM/ResInsight/issues/7686 std::map> segmentsInCell; { for ( auto s : allSegments ) { auto cellsIntersected = s->globalCellsIntersected(); if ( !cellsIntersected.empty() ) { for ( auto index : cellsIntersected ) { segmentsInCell[index].insert( s ); } } } } for ( auto [index, segmentsInSameCell] : segmentsInCell ) { // Compute effective diameter based on square root of the sum of diameter squared // Deff = sqrt(d1^2 + d2^2 + ..) double effectiveDiameter = 0.0; for ( auto seg : segmentsInSameCell ) { effectiveDiameter += ( seg->equivalentDiameter() * seg->equivalentDiameter() ); } effectiveDiameter = sqrt( effectiveDiameter ); for ( auto seg : segmentsInSameCell ) { seg->setEffectiveDiameter( effectiveDiameter ); } } { // Reduce the diameter for segments in the same cell as main bore // https://github.com/OPM/ResInsight/issues/7731 for ( auto s : allSegments ) { for ( auto completion : s->completions() ) { if ( completion->completionType() == RigCompletionData::CompletionType::FISHBONES ) { auto segments = completion->segments(); if ( segments.size() > 1 ) { auto firstSegment = segments[0]; auto secondSegment = segments[1]; double diameter = secondSegment->effectiveDiameter(); firstSegment->setEffectiveDiameter( diameter ); } } } } } } { // Update IDC area std::map> icdsInCell; { for ( auto s : allSegments ) { for ( auto completion : s->completions() ) { if ( auto icd = dynamic_cast( completion ) ) { for ( auto icdSegment : icd->segments() ) { for ( auto gridIntersection : icdSegment->intersections() ) { icdsInCell[gridIntersection->globalCellIndex()].insert( icd ); } } } } } } for ( auto [Index, icdsInSameCell] : icdsInCell ) { // Compute area sum for all ICDs in same grid cell double areaSum = 0.0; for ( auto icd : icdsInSameCell ) { areaSum += icd->area(); } for ( auto icd : icdsInSameCell ) { icd->setArea( areaSum ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::generateFishbonesMswExportInfo( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath, double initialMD, const std::vector& cellIntersections, bool enableSegmentSplitting, gsl::not_null exportInfo, gsl::not_null branch ) { std::vector fishbonesSubs = wellPath->fishbonesCollection()->activeFishbonesSubs(); if ( fishbonesSubs.empty() ) return; generateFishbonesMswExportInfo( eclipseCase, wellPath, initialMD, cellIntersections, fishbonesSubs, enableSegmentSplitting, exportInfo, branch ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::generateFishbonesMswExportInfo( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath, double initialMD, const std::vector& cellIntersections, const std::vector& fishbonesSubs, bool enableSegmentSplitting, gsl::not_null exportInfo, gsl::not_null branch ) { std::vector filteredIntersections = filterIntersections( cellIntersections, initialMD, wellPath->wellPathGeometry(), eclipseCase ); auto mswParameters = wellPath->mswCompletionParameters(); bool foundSubGridIntersections = false; // Create a dummy perforation interval RimPerforationInterval perfInterval; createWellPathSegments( branch, filteredIntersections, { &perfInterval }, wellPath, -1, eclipseCase, &foundSubGridIntersections ); double maxSegmentLength = enableSegmentSplitting ? mswParameters->maxSegmentLength() : std::numeric_limits::infinity(); double subStartMD = wellPath->fishbonesCollection()->startMD(); double subStartTVD = RicMswTableFormatterTools::tvdFromMeasuredDepth( branch->wellPath(), subStartMD ); auto unitSystem = exportInfo->unitSystem(); for ( RimFishbones* subs : fishbonesSubs ) { std::map> subAndLateralIndices; for ( const auto& [subIndex, lateralIndex] : subs->installedLateralIndices() ) { subAndLateralIndices[subIndex].push_back( lateralIndex ); } // Find cell intersections closest to each sub location std::map> closestSubForCellIntersections; std::map cellIntersectionContainingSubIndex; { auto fishboneSectionStart = subs->startMD(); auto fishboneSectionEnd = subs->endMD(); for ( size_t intersectionIndex = 0; intersectionIndex < filteredIntersections.size(); intersectionIndex++ ) { const auto& cellIntersection = filteredIntersections[intersectionIndex]; if ( ( fishboneSectionEnd >= cellIntersection.startMD ) && ( fishboneSectionStart <= cellIntersection.endMD ) ) { double intersectionMidpoint = 0.5 * ( cellIntersection.startMD + cellIntersection.endMD ); size_t closestSubIndex = 0; double closestDistance = std::numeric_limits::infinity(); for ( const auto& [subIndex, lateralIndices] : subAndLateralIndices ) { double subMD = subs->measuredDepth( subIndex ); if ( ( cellIntersection.startMD <= subMD ) && ( subMD <= cellIntersection.endMD ) ) { cellIntersectionContainingSubIndex[subIndex] = intersectionIndex; } auto distanceCandicate = std::abs( subMD - intersectionMidpoint ); if ( distanceCandicate < closestDistance ) { closestDistance = distanceCandicate; closestSubIndex = subIndex; } } closestSubForCellIntersections[closestSubIndex].push_back( intersectionIndex ); } } } for ( const auto& [subIndex, lateralIndices] : subAndLateralIndices ) { double subEndMD = subs->measuredDepth( subIndex ); double subEndTVD = RicMswTableFormatterTools::tvdFromMeasuredDepth( branch->wellPath(), subEndMD ); { // Add completion for ICD auto icdSegment = std::make_unique( "ICD segment", subEndMD, subEndMD + 0.1, subEndTVD, subEndTVD, subIndex ); for ( auto lateralIndex : lateralIndices ) { QString label = QString( "Lateral %1" ).arg( lateralIndex + 1 ); icdSegment->addCompletion( std::make_unique( label, wellPath, subEndMD, subEndTVD, lateralIndex ) ); } assignFishbonesLateralIntersections( eclipseCase, branch->wellPath(), subs, icdSegment.get(), &foundSubGridIntersections, maxSegmentLength, unitSystem ); auto icdCompletion = std::make_unique( QString( "ICD" ), wellPath, subEndMD, subEndTVD, nullptr ); icdCompletion->setFlowCoefficient( subs->icdFlowCoefficient() ); double icdOrificeRadius = subs->icdOrificeDiameter( unitSystem ) / 2; icdCompletion->setArea( icdOrificeRadius * icdOrificeRadius * cvf::PI_D * subs->icdCount() ); // assign open hole segments to sub { const RigMainGrid* mainGrid = eclipseCase->mainGrid(); std::set indices; for ( auto intersectionIndex : closestSubForCellIntersections[subIndex] ) { indices.insert( intersectionIndex ); } indices.insert( cellIntersectionContainingSubIndex[subIndex] ); for ( auto intersectionIndex : indices ) { auto intersection = filteredIntersections[intersectionIndex]; if ( intersection.globCellIndex >= mainGrid->globalCellArray().size() ) continue; size_t localGridCellIndex = 0u; const RigGridBase* localGrid = mainGrid->gridAndGridLocalIdxFromGlobalCellIdx( intersection.globCellIndex, &localGridCellIndex ); QString gridName; if ( localGrid != mainGrid ) { gridName = QString::fromStdString( localGrid->gridName() ); foundSubGridIntersections = true; } size_t i, j, k; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); cvf::Vec3st localIJK( i, j, k ); auto mswIntersect = std::make_shared( gridName, intersection.globCellIndex, localIJK, intersection.intersectionLengthsInCellCS ); icdSegment->addIntersection( mswIntersect ); } } icdCompletion->addSegment( std::move( icdSegment ) ); RicMswSegment* segmentOnParentBranch = branch->findClosestSegmentWithLowerMD( subEndMD ); if ( segmentOnParentBranch ) { segmentOnParentBranch->addCompletion( std::move( icdCompletion ) ); } } subStartMD = subEndMD; subStartTVD = subEndTVD; } } exportInfo->setHasSubGridIntersections( exportInfo->hasSubGridIntersections() || foundSubGridIntersections ); // branch->sortSegments(); auto connectedWellPaths = wellPathsWithTieIn( wellPath ); for ( auto childWellPath : connectedWellPaths ) { auto childMswBranch = createChildMswBranch( childWellPath ); auto childCellIntersections = generateCellSegments( eclipseCase, childWellPath ); generateFishbonesMswExportInfo( eclipseCase, childWellPath, initialMD, childCellIntersections, enableSegmentSplitting, exportInfo, childMswBranch.get() ); branch->addChildBranch( std::move( childMswBranch ) ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::generateFishbonesMswExportInfoForWell( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath, gsl::not_null exportInfo, gsl::not_null branch ) { auto mswParameters = wellPath->mswCompletionParameters(); auto cellIntersections = generateCellSegments( eclipseCase, wellPath ); double initialMD = computeIntitialMeasuredDepth( eclipseCase, wellPath, mswParameters, cellIntersections ); RiaDefines::EclipseUnitSystem unitSystem = eclipseCase->eclipseCaseData()->unitsType(); bool enableSegmentSplitting = false; generateFishbonesMswExportInfo( eclipseCase, wellPath, initialMD, cellIntersections, enableSegmentSplitting, exportInfo, branch ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RicWellPathExportMswCompletionsImpl::generateFracturesMswExportInfo( RimEclipseCase* eclipseCase, const RimWellPath* wellPath, double initialMD, const std::vector& cellIntersections, gsl::not_null exportInfo, gsl::not_null branch ) { auto mswParameters = wellPath->mswCompletionParameters(); auto fractures = wellPath->fractureCollection()->activeFractures(); std::vector filteredIntersections = filterIntersections( cellIntersections, initialMD, wellPath->wellPathGeometry(), eclipseCase ); // Create a dummy perforation interval RimPerforationInterval perfInterval; bool foundSubGridIntersections = false; createWellPathSegments( branch, filteredIntersections, { &perfInterval }, wellPath, -1, eclipseCase, &foundSubGridIntersections ); // Check if fractures are to be assigned to current main bore segment for ( RimWellPathFracture* fracture : fractures ) { double fractureStartMD = fracture->fractureMD(); if ( fracture->fractureTemplate()->orientationType() == RimFractureTemplate::ALONG_WELL_PATH ) { double perforationLength = fracture->fractureTemplate()->perforationLength(); fractureStartMD -= 0.5 * perforationLength; } auto segment = branch->findClosestSegmentWithLowerMD( fractureStartMD ); if ( segment ) { std::vector completionData = RicExportFractureCompletionsImpl::generateCompdatValues( eclipseCase, wellPath->completionSettings()->wellNameForExport(), wellPath->wellPathGeometry(), { fracture }, nullptr, nullptr ); assignFractureCompletionsToCellSegment( eclipseCase, wellPath, fracture, completionData, segment, &foundSubGridIntersections ); } } exportInfo->setHasSubGridIntersections( exportInfo->hasSubGridIntersections() || foundSubGridIntersections ); branch->sortSegments(); auto connectedWellPaths = wellPathsWithTieIn( wellPath ); for ( auto childWellPath : connectedWellPaths ) { auto childMswBranch = createChildMswBranch( childWellPath ); auto childCellIntersections = generateCellSegments( eclipseCase, childWellPath ); if ( generateFracturesMswExportInfo( eclipseCase, childWellPath, initialMD, childCellIntersections, exportInfo, childMswBranch.get() ) ) { branch->addChildBranch( std::move( childMswBranch ) ); } } return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RicWellPathExportMswCompletionsImpl::generatePerforationsMswExportInfo( RimEclipseCase* eclipseCase, const RimWellPath* wellPath, int timeStep, double initialMD, const std::vector& cellIntersections, gsl::not_null exportInfo, gsl::not_null branch ) { auto perforationIntervals = wellPath->perforationIntervalCollection()->activePerforations(); // Check if there exist overlap between valves in a perforation interval for ( const auto& perfInterval : perforationIntervals ) { for ( const auto& valve : perfInterval->valves() ) { for ( const auto& otherValve : perfInterval->valves() ) { if ( otherValve != valve ) { bool hasIntersection = !( ( valve->endMD() < otherValve->startMD() ) || ( otherValve->endMD() < valve->startMD() ) ); if ( hasIntersection ) { RiaLogging::error( QString( "Valve overlap detected for perforation interval : %1" ).arg( perfInterval->name() ) ); RiaLogging::error( "Name of valves" ); RiaLogging::error( valve->name() ); RiaLogging::error( otherValve->name() ); RiaLogging::error( "Failed to export well segments" ); return false; } } } } } std::vector filteredIntersections = filterIntersections( cellIntersections, initialMD, wellPath->wellPathGeometry(), eclipseCase ); bool foundSubGridIntersections = false; createWellPathSegments( branch, filteredIntersections, perforationIntervals, wellPath, timeStep, eclipseCase, &foundSubGridIntersections ); createValveCompletions( branch, perforationIntervals, wellPath, exportInfo->unitSystem() ); const RigActiveCellInfo* activeCellInfo = eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); assignValveContributionsToSuperICDsOrAICDs( branch, perforationIntervals, filteredIntersections, activeCellInfo, exportInfo->unitSystem() ); moveIntersectionsToICVs( branch, perforationIntervals, exportInfo->unitSystem() ); moveIntersectionsToSuperICDsOrAICDs( branch ); exportInfo->setHasSubGridIntersections( exportInfo->hasSubGridIntersections() || foundSubGridIntersections ); branch->sortSegments(); auto connectedWellPaths = wellPathsWithTieIn( wellPath ); for ( auto childWellPath : connectedWellPaths ) { auto childMswBranch = createChildMswBranch( childWellPath ); auto childCellIntersections = generateCellSegments( eclipseCase, childWellPath ); if ( generatePerforationsMswExportInfo( eclipseCase, childWellPath, timeStep, initialMD, childCellIntersections, exportInfo, childMswBranch.get() ) ) { branch->addChildBranch( std::move( childMswBranch ) ); } } return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportMswCompletionsImpl::generateCellSegments( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath ) { const RigActiveCellInfo* activeCellInfo = eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); auto wellPathGeometry = wellPath->wellPathGeometry(); CVF_ASSERT( wellPathGeometry ); const std::vector& coords = wellPathGeometry->uniqueWellPathPoints(); const std::vector& mds = wellPathGeometry->uniqueMeasuredDepths(); CVF_ASSERT( !coords.empty() && !mds.empty() ); const RigMainGrid* mainGrid = eclipseCase->mainGrid(); std::vector allIntersections = RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( eclipseCase->eclipseCaseData(), wellPath->name(), coords, mds ); if ( allIntersections.empty() ) return {}; std::vector continuousIntersections = RigWellPathIntersectionTools::buildContinuousIntersections( allIntersections, mainGrid ); return continuousIntersections; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RicWellPathExportMswCompletionsImpl::computeIntitialMeasuredDepth( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath, const RimMswCompletionParameters* mswParameters, const std::vector& allIntersections ) { if ( allIntersections.empty() ) return 0.0; const RigActiveCellInfo* activeCellInfo = eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); double candidateMeasuredDepth = 0.0; if ( mswParameters->referenceMDType() == RimMswCompletionParameters::ReferenceMDType::MANUAL_REFERENCE_MD ) { candidateMeasuredDepth = mswParameters->manualReferenceMD(); } else { for ( const WellPathCellIntersectionInfo& intersection : allIntersections ) { if ( activeCellInfo->isActive( intersection.globCellIndex ) ) { candidateMeasuredDepth = intersection.startMD; break; } } double startOfFirstCompletion = std::numeric_limits::infinity(); { std::vector allCompletions = wellPath->completions()->allCompletions(); for ( const RimWellPathComponentInterface* completion : allCompletions ) { if ( completion->isEnabled() && completion->startMD() < startOfFirstCompletion ) { startOfFirstCompletion = completion->startMD(); } } } // Initial MD is the lowest MD based on grid intersection and start of fracture completions // https://github.com/OPM/ResInsight/issues/6071 candidateMeasuredDepth = std::min( candidateMeasuredDepth, startOfFirstCompletion ); } return candidateMeasuredDepth; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportMswCompletionsImpl::filterIntersections( const std::vector& intersections, double initialMD, gsl::not_null wellPathGeometry, gsl::not_null eclipseCase ) { std::vector filteredIntersections; if ( !intersections.empty() && intersections[0].startMD > initialMD ) { WellPathCellIntersectionInfo firstIntersection = intersections[0]; // Add a segment from user defined MD to start of grid cvf::Vec3d intersectionPoint = wellPathGeometry->interpolatedPointAlongWellPath( initialMD ); WellPathCellIntersectionInfo extraIntersection; extraIntersection.globCellIndex = std::numeric_limits::max(); extraIntersection.startPoint = intersectionPoint; extraIntersection.endPoint = firstIntersection.startPoint; extraIntersection.startMD = initialMD; extraIntersection.endMD = firstIntersection.startMD; extraIntersection.intersectedCellFaceIn = cvf::StructGridInterface::NO_FACE; if ( firstIntersection.intersectedCellFaceIn != cvf::StructGridInterface::NO_FACE ) { extraIntersection.intersectedCellFaceOut = cvf::StructGridInterface::oppositeFace( firstIntersection.intersectedCellFaceIn ); } else if ( firstIntersection.intersectedCellFaceOut != cvf::StructGridInterface::NO_FACE ) { extraIntersection.intersectedCellFaceOut = firstIntersection.intersectedCellFaceOut; } extraIntersection.intersectionLengthsInCellCS = cvf::Vec3d::ZERO; filteredIntersections.push_back( extraIntersection ); } const double epsilon = 1.0e-3; for ( const WellPathCellIntersectionInfo& intersection : intersections ) { if ( ( intersection.endMD - initialMD ) < epsilon ) { // Skip all intersections before initial measured depth continue; } if ( ( intersection.startMD - initialMD ) > epsilon ) { filteredIntersections.push_back( intersection ); } else { // InitialMD is inside intersection, split based on intersection point cvf::Vec3d intersectionPoint = wellPathGeometry->interpolatedPointAlongWellPath( initialMD ); WellPathCellIntersectionInfo extraIntersection; extraIntersection.globCellIndex = intersection.globCellIndex; extraIntersection.startPoint = intersectionPoint; extraIntersection.endPoint = intersection.endPoint; extraIntersection.startMD = initialMD; extraIntersection.endMD = intersection.endMD; extraIntersection.intersectedCellFaceIn = cvf::StructGridInterface::NO_FACE; extraIntersection.intersectedCellFaceOut = intersection.intersectedCellFaceOut; const RigMainGrid* grid = eclipseCase->mainGrid(); if ( intersection.globCellIndex < grid->cellCount() ) { extraIntersection.intersectionLengthsInCellCS = RigWellPathIntersectionTools::calculateLengthInCell( grid, intersection.globCellIndex, intersectionPoint, intersection.endPoint ); } else { extraIntersection.intersectionLengthsInCellCS = cvf::Vec3d::ZERO; } filteredIntersections.push_back( extraIntersection ); } } return filteredIntersections; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::createWellPathSegments( gsl::not_null branch, const std::vector& cellSegmentIntersections, const std::vector& perforationIntervals, const RimWellPath* wellPath, int timeStep, const RimEclipseCase* eclipseCase, bool* foundSubGridIntersections ) { // Intersections along the well path with grid geometry is handled by well log extraction tools. // The threshold in RigWellLogExtractionTools::isEqualDepth is currently set to 0.1m, and this // is a pretty large threshold based on the indicated threshold of 0.001m for MSW segments const double segmentLengthThreshold = 1.0e-3; for ( const auto& cellIntInfo : cellSegmentIntersections ) { const double segmentLength = std::fabs( cellIntInfo.endMD - cellIntInfo.startMD ); if ( segmentLength > segmentLengthThreshold ) { auto segment = std::make_unique( QString( "%1 segment" ).arg( branch->label() ), cellIntInfo.startMD, cellIntInfo.endMD, cellIntInfo.startTVD(), cellIntInfo.endTVD() ); for ( const RimPerforationInterval* interval : perforationIntervals ) { double overlapStart = std::max( interval->startMD(), segment->startMD() ); double overlapEnd = std::min( interval->endMD(), segment->endMD() ); double overlap = std::max( 0.0, overlapEnd - overlapStart ); if ( overlap > 0.0 ) { double overlapStartTVD = -wellPath->wellPathGeometry()->interpolatedPointAlongWellPath( overlapStart ).z(); auto intervalCompletion = std::make_unique( interval->name(), wellPath, overlapStart, overlapStartTVD ); std::vector completionData = generatePerforationIntersections( wellPath, interval, timeStep, eclipseCase ); assignPerforationIntersections( completionData, intervalCompletion.get(), cellIntInfo, overlapStart, overlapEnd, foundSubGridIntersections ); segment->addCompletion( std::move( intervalCompletion ) ); } } branch->addSegment( std::move( segment ) ); } else { QString text = QString( "Skipping segment , threshold = %1, length = %2" ).arg( segmentLengthThreshold ).arg( segmentLength ); RiaLogging::info( text ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::createValveCompletions( gsl::not_null branch, const std::vector& perforationIntervals, const RimWellPath* wellPath, RiaDefines::EclipseUnitSystem unitSystem ) { int nMainSegment = 0; auto segments = branch->segments(); for ( auto segment : segments ) { std::unique_ptr ICV; std::unique_ptr superICD; std::unique_ptr superAICD; double totalICDOverlap = 0.0; double totalAICDOverlap = 0.0; for ( const RimPerforationInterval* interval : perforationIntervals ) { if ( !interval->isChecked() ) continue; std::vector perforationValves; interval->descendantsIncludingThisOfType( perforationValves ); for ( const RimWellPathValve* valve : perforationValves ) { if ( !valve->isChecked() ) continue; for ( size_t nSubValve = 0u; nSubValve < valve->valveLocations().size(); ++nSubValve ) { double valveMD = valve->valveLocations()[nSubValve]; std::pair valveSegment = valve->valveSegments()[nSubValve]; double overlapStart = std::max( valveSegment.first, segment->startMD() ); double overlapEnd = std::min( valveSegment.second, segment->endMD() ); double overlap = std::max( 0.0, overlapEnd - overlapStart ); double exportStartMD = valveMD; double exportEndMD = valveMD + 0.1; double exportStartTVD = RicMswTableFormatterTools::tvdFromMeasuredDepth( wellPath, exportStartMD ); double exportEndTVD = RicMswTableFormatterTools::tvdFromMeasuredDepth( wellPath, exportEndMD ); double overlapStartTVD = RicMswTableFormatterTools::tvdFromMeasuredDepth( wellPath, overlapStart ); double overlapEndTVD = RicMswTableFormatterTools::tvdFromMeasuredDepth( wellPath, overlapEnd ); if ( segment->startMD() <= valveMD && valveMD < segment->endMD() ) { if ( valve->componentType() == RiaDefines::WellPathComponentType::AICD ) { QString valveLabel = QString( "%1 #%2" ).arg( "Combined Valve for segment" ).arg( nMainSegment + 2 ); auto subSegment = std::make_unique( "Valve segment", exportStartMD, exportEndMD, exportStartTVD, exportEndTVD ); superAICD = std::make_unique( valveLabel, wellPath, exportStartMD, exportStartTVD, valve ); superAICD->addSegment( std::move( subSegment ) ); } else if ( valve->componentType() == RiaDefines::WellPathComponentType::ICD ) { QString valveLabel = QString( "%1 #%2" ).arg( "Combined Valve for segment" ).arg( nMainSegment + 2 ); auto subSegment = std::make_unique( "Valve segment", exportStartMD, exportEndMD, exportStartTVD, exportEndTVD ); superICD = std::make_unique( valveLabel, wellPath, exportStartMD, exportStartTVD, valve ); superICD->addSegment( std::move( subSegment ) ); } else if ( valve->componentType() == RiaDefines::WellPathComponentType::ICV ) { QString valveLabel = QString( "ICV %1 at segment #%2" ).arg( valve->name() ).arg( nMainSegment + 2 ); auto subSegment = std::make_unique( "Valve segment", exportStartMD, exportEndMD, exportStartTVD, exportEndTVD ); ICV = std::make_unique( valveLabel, wellPath, exportStartMD, exportStartTVD, valve ); ICV->addSegment( std::move( subSegment ) ); } } else if ( overlap > 0.0 && ( valve->componentType() == RiaDefines::WellPathComponentType::ICD && !superICD ) ) { QString valveLabel = QString( "%1 #%2" ).arg( "Combined Valve for segment" ).arg( nMainSegment + 2 ); auto subSegment = std::make_unique( "Valve segment", exportStartMD, exportEndMD, exportStartTVD, exportEndTVD ); superICD = std::make_unique( valveLabel, wellPath, exportStartMD, exportStartTVD, valve ); superICD->addSegment( std::move( subSegment ) ); } else if ( overlap > 0.0 && ( valve->componentType() == RiaDefines::WellPathComponentType::AICD && !superAICD ) ) { QString valveLabel = QString( "%1 #%2" ).arg( "Combined Valve for segment" ).arg( nMainSegment + 2 ); auto subSegment = std::make_unique( "Valve segment", exportStartMD, exportEndMD, exportStartTVD, exportEndTVD ); superAICD = std::make_unique( valveLabel, wellPath, exportStartMD, exportStartTVD, valve ); superAICD->addSegment( std::move( subSegment ) ); } if ( valve->componentType() == RiaDefines::WellPathComponentType::AICD ) { totalAICDOverlap += overlap; } else if ( valve->componentType() == RiaDefines::WellPathComponentType::ICD ) { totalICDOverlap += overlap; } } } } if ( ICV ) { segment->addCompletion( std::move( ICV ) ); } else { if ( totalICDOverlap > 0.0 || totalAICDOverlap > 0.0 ) { if ( totalAICDOverlap > totalICDOverlap ) { segment->addCompletion( std::move( superAICD ) ); } else { segment->addCompletion( std::move( superICD ) ); } } } nMainSegment++; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignValveContributionsToSuperICDsOrAICDs( gsl::not_null branch, const std::vector& perforationIntervals, const std::vector& wellPathIntersections, const RigActiveCellInfo* activeCellInfo, RiaDefines::EclipseUnitSystem unitSystem ) { using ValveContributionMap = std::map>; ValveContributionMap assignedRegularValves; std::map> accumulators; for ( auto segment : branch->segments() ) { RicMswValve* superValve = nullptr; for ( auto completion : segment->completions() ) { auto valve = dynamic_cast( completion ); if ( valve ) { superValve = valve; break; } } if ( dynamic_cast( superValve ) ) { accumulators[segment] = std::make_unique( superValve, unitSystem ); } else if ( dynamic_cast( superValve ) ) { accumulators[segment] = std::make_unique( superValve, unitSystem ); } } for ( const RimPerforationInterval* interval : perforationIntervals ) { if ( !interval->isChecked() ) continue; std::vector perforationValves; interval->descendantsIncludingThisOfType( perforationValves ); double totalPerforationLength = 0.0; for ( const RimWellPathValve* valve : perforationValves ) { if ( !valve->isChecked() ) continue; for ( auto segment : branch->segments() ) { double intervalOverlapStart = std::max( interval->startMD(), segment->startMD() ); double intervalOverlapEnd = std::min( interval->endMD(), segment->endMD() ); auto intervalOverlapWithActiveCells = calculateOverlapWithActiveCells( intervalOverlapStart, intervalOverlapEnd, wellPathIntersections, activeCellInfo ); totalPerforationLength += intervalOverlapWithActiveCells.second - intervalOverlapWithActiveCells.first; } } for ( const RimWellPathValve* valve : perforationValves ) { if ( !valve->isChecked() ) continue; for ( auto segment : branch->segments() ) { double intervalOverlapStart = std::max( interval->startMD(), segment->startMD() ); double intervalOverlapEnd = std::min( interval->endMD(), segment->endMD() ); auto intervalOverlapWithActiveCells = calculateOverlapWithActiveCells( intervalOverlapStart, intervalOverlapEnd, wellPathIntersections, activeCellInfo ); double overlapLength = intervalOverlapWithActiveCells.second - intervalOverlapWithActiveCells.first; if ( overlapLength > 0.0 ) { auto it = accumulators.find( segment ); if ( it != accumulators.end() ) { it->second->accumulateValveParameters( valve, overlapLength, totalPerforationLength ); assignedRegularValves[it->second->superValve()].push_back( valve ); } } } } } for ( const auto& accumulator : accumulators ) { accumulator.second->applyToSuperValve(); } for ( auto regularValvePair : assignedRegularValves ) { if ( !regularValvePair.second.empty() ) { QStringList valveLabels; for ( const RimWellPathValve* regularValve : regularValvePair.second ) { QString valveLabel = QString( "%1" ).arg( regularValve->name() ); valveLabels.push_back( valveLabel ); } QString valveContribLabel = QString( " with contribution from: %1" ).arg( valveLabels.join( ", " ) ); regularValvePair.first->setLabel( regularValvePair.first->label() + valveContribLabel ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::moveIntersectionsToICVs( gsl::not_null branch, const std::vector& perforationIntervals, RiaDefines::EclipseUnitSystem unitSystem ) { std::map icvCompletionMap; for ( auto segment : branch->segments() ) { for ( auto completion : segment->completions() ) { auto icv = dynamic_cast( completion ); if ( icv ) { icvCompletionMap[icv->wellPathValve()] = icv; } } } for ( auto segment : branch->segments() ) { std::vector perforations; for ( auto completion : segment->completions() ) { if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION ) { perforations.push_back( completion ); } } for ( const RimPerforationInterval* interval : perforationIntervals ) { if ( !interval->isChecked() ) continue; std::vector perforationValves; interval->descendantsIncludingThisOfType( perforationValves ); for ( const RimWellPathValve* valve : perforationValves ) { if ( !valve->isChecked() ) continue; if ( valve->componentType() != RiaDefines::WellPathComponentType::ICV ) continue; auto icvIt = icvCompletionMap.find( valve ); if ( icvIt == icvCompletionMap.end() ) continue; auto icvCompletion = icvIt->second; CVF_ASSERT( icvCompletion ); std::pair valveSegment = valve->valveSegments().front(); double overlapStart = std::max( valveSegment.first, segment->startMD() ); double overlapEnd = std::min( valveSegment.second, segment->endMD() ); double overlap = std::max( 0.0, overlapEnd - overlapStart ); if ( overlap > 0.0 ) { CVF_ASSERT( icvCompletion->segments().size() == 1u ); for ( auto perforationPtr : perforations ) { for ( auto subSegmentPtr : perforationPtr->segments() ) { for ( auto intersectionPtr : subSegmentPtr->intersections() ) { icvCompletion->segments()[0]->addIntersection( intersectionPtr ); } } segment->removeCompletion( perforationPtr ); } } } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::moveIntersectionsToSuperICDsOrAICDs( gsl::not_null branch ) { for ( auto segment : branch->segments() ) { RicMswCompletion* superValve = nullptr; std::vector perforations; for ( auto completion : segment->completions() ) { if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD || completion->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD ) { superValve = completion; } else if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION ) { perforations.push_back( completion ); } } if ( superValve == nullptr ) continue; CVF_ASSERT( superValve->segments().size() == 1u ); // Remove and take over ownership of the superValve completion auto completionPtr = segment->removeCompletion( superValve ); for ( auto perforation : perforations ) { for ( auto subSegment : perforation->segments() ) { // The valve completions on the main branch will be deleted. Create a segment with startMD and // endMD representing the perforation along main well path to be connected to the valve. When COMPSEGS // data is exported, the startMD and endMD of the segment is used to define the Start Length and End // Length of the COMPSEGS keyword // // Example output // // COMPSEGS // --Name // Well - 1 / // --I J K Branch no Start Length End Length // 17 17 9 2 3030.71791 3034.01331 / // 17 18 9 3 3034.01331 3125.47617 / auto valveInflowSegment = std::make_unique( QString( "%1 real valve segment " ).arg( branch->label() ), subSegment->startMD(), subSegment->endMD(), subSegment->startTVD(), subSegment->endTVD() ); for ( auto intersectionPtr : subSegment->intersections() ) { valveInflowSegment->addIntersection( intersectionPtr ); } { double midpoint = ( segment->startMD() + segment->endMD() ) * 0.5; // Set the output MD to the midpoint of the segment, this info is used when exporting WELSEGS in // RicMswTableFormatterTools::writeValveWelsegsSegment completionPtr->segments()[0]->setOutputMD( midpoint ); } completionPtr->addSegment( std::move( valveInflowSegment ) ); } } // Remove all completions and re-add the super valve segment->deleteAllCompletions(); segment->addCompletion( std::move( completionPtr ) ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignFishbonesLateralIntersections( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath, const RimFishbones* fishbonesSubs, gsl::not_null segment, bool* foundSubGridIntersections, double maxSegmentLength, RiaDefines::EclipseUnitSystem unitSystem ) { CVF_ASSERT( foundSubGridIntersections != nullptr ); const RigMainGrid* grid = eclipseCase->eclipseCaseData()->mainGrid(); for ( auto completion : segment->completions() ) { if ( completion->completionType() != RigCompletionData::CompletionType::FISHBONES ) { continue; } std::vector> lateralCoordMDPairs = fishbonesSubs->coordsAndMDForLateral( segment->subIndex(), completion->index() ); if ( lateralCoordMDPairs.empty() ) { continue; } std::vector lateralCoords; std::vector lateralMDs; lateralCoords.reserve( lateralCoordMDPairs.size() ); lateralMDs.reserve( lateralCoordMDPairs.size() ); for ( auto& coordMD : lateralCoordMDPairs ) { lateralCoords.push_back( coordMD.first ); lateralMDs.push_back( coordMD.second ); } std::vector intersections = RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( eclipseCase->eclipseCaseData(), wellPath->name(), lateralCoords, lateralMDs ); RigWellPath pathGeometry( lateralCoords, lateralMDs ); double previousExitMD = lateralMDs.front(); double previousExitTVD = -lateralCoords.front().z(); for ( const auto& cellIntInfo : intersections ) { size_t localGridCellIndex = 0u; const RigGridBase* localGrid = grid->gridAndGridLocalIdxFromGlobalCellIdx( cellIntInfo.globCellIndex, &localGridCellIndex ); QString gridName; if ( localGrid != grid ) { gridName = QString::fromStdString( localGrid->gridName() ); *foundSubGridIntersections = true; } size_t i = 0u, j = 0u, k = 0u; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); auto subSegment = std::make_unique( "Sub segment", previousExitMD, cellIntInfo.endMD, previousExitTVD, cellIntInfo.endTVD(), segment->subIndex() ); subSegment->setEquivalentDiameter( fishbonesSubs->equivalentDiameter( unitSystem ) ); subSegment->setHoleDiameter( fishbonesSubs->holeDiameter( unitSystem ) ); subSegment->setOpenHoleRoughnessFactor( fishbonesSubs->openHoleRoughnessFactor( unitSystem ) ); subSegment->setSkinFactor( fishbonesSubs->skinFactor() ); subSegment->setSourcePdmObject( fishbonesSubs ); subSegment->setIntersectedGlobalCells( { cellIntInfo.globCellIndex } ); auto intersection = std::make_shared( gridName, cellIntInfo.globCellIndex, cvf::Vec3st( i, j, k ), cellIntInfo.intersectionLengthsInCellCS ); subSegment->addIntersection( std::move( intersection ) ); completion->addSegment( std::move( subSegment ) ); previousExitMD = cellIntInfo.endMD; previousExitTVD = cellIntInfo.endTVD(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignFractureCompletionsToCellSegment( const RimEclipseCase* eclipseCase, const RimWellPath* wellPath, const RimWellPathFracture* fracture, const std::vector& completionData, gsl::not_null segment, bool* foundSubGridIntersections ) { CVF_ASSERT( foundSubGridIntersections != nullptr ); double position = fracture->fractureMD(); double width = fracture->fractureTemplate()->computeFractureWidth( fracture ); auto fractureCompletion = std::make_unique( fracture->name(), wellPath, position, position + width ); if ( fracture->fractureTemplate()->orientationType() == RimFractureTemplate::ALONG_WELL_PATH ) { double perforationLength = fracture->fractureTemplate()->perforationLength(); position -= 0.5 * perforationLength; width = perforationLength; } auto subSegment = std::make_unique( "Fracture segment", position, position + width, 0.0, 0.0 ); for ( const RigCompletionData& compIntersection : completionData ) { const RigCompletionDataGridCell& cell = compIntersection.completionDataGridCell(); if ( !cell.isMainGridCell() ) { *foundSubGridIntersections = true; } cvf::Vec3st localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); auto intersection = std::make_shared( cell.lgrName(), cell.globalCellIndex(), localIJK, cvf::Vec3d::ZERO ); subSegment->addIntersection( intersection ); } fractureCompletion->addSegment( std::move( subSegment ) ); segment->addCompletion( std::move( fractureCompletion ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportMswCompletionsImpl::generatePerforationIntersections( gsl::not_null wellPath, gsl::not_null perforationInterval, int timeStep, gsl::not_null eclipseCase ) { std::vector completionData; const RigActiveCellInfo* activeCellInfo = eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); auto wellPathGeometry = wellPath->wellPathGeometry(); CVF_ASSERT( wellPathGeometry ); bool hasDate = (size_t)timeStep < eclipseCase->timeStepDates().size(); bool isActive = !hasDate || perforationInterval->isActiveOnDate( eclipseCase->timeStepDates()[timeStep] ); if ( wellPath->perforationIntervalCollection()->isChecked() && perforationInterval->isChecked() && isActive ) { std::pair, std::vector> perforationPointsAndMD = wellPathGeometry->clippedPointSubset( perforationInterval->startMD(), perforationInterval->endMD() ); std::vector intersectedCells = RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( eclipseCase->eclipseCaseData(), wellPath->name(), perforationPointsAndMD.first, perforationPointsAndMD.second ); for ( auto& cell : intersectedCells ) { bool cellIsActive = activeCellInfo->isActive( cell.globCellIndex ); if ( !cellIsActive ) continue; RigCompletionData completion( wellPath->completionSettings()->wellNameForExport(), RigCompletionDataGridCell( cell.globCellIndex, eclipseCase->mainGrid() ), cell.startMD ); completion.setSourcePdmObject( perforationInterval ); completionData.push_back( completion ); } } return completionData; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignPerforationIntersections( const std::vector& completionData, gsl::not_null perforationCompletion, const WellPathCellIntersectionInfo& cellIntInfo, double overlapStart, double overlapEnd, bool* foundSubGridIntersections ) { size_t currCellId = cellIntInfo.globCellIndex; auto subSegment = std::make_unique( "Perforation segment", overlapStart, overlapEnd, cellIntInfo.startTVD(), cellIntInfo.endTVD() ); for ( const RigCompletionData& compIntersection : completionData ) { const RigCompletionDataGridCell& cell = compIntersection.completionDataGridCell(); if ( !cell.isMainGridCell() ) { *foundSubGridIntersections = true; } if ( cell.globalCellIndex() != currCellId ) continue; cvf::Vec3st localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); auto intersection = std::make_shared( cell.lgrName(), cell.globalCellIndex(), localIJK, cellIntInfo.intersectionLengthsInCellCS ); subSegment->addIntersection( intersection ); } perforationCompletion->addSegment( std::move( subSegment ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignBranchNumbersToPerforations( const RimEclipseCase* eclipseCase, gsl::not_null segment, gsl::not_null branchNumber ) { for ( auto completion : segment->completions() ) { if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION ) { completion->setBranchNumber( *branchNumber ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignBranchNumbersToOtherCompletions( const RimEclipseCase* eclipseCase, gsl::not_null segment, gsl::not_null branchNumber ) { for ( auto completion : segment->completions() ) { if ( completion->completionType() != RigCompletionData::CompletionType::PERFORATION ) { completion->setBranchNumber( ++( *branchNumber ) ); for ( auto seg : completion->segments() ) { assignBranchNumbersToOtherCompletions( eclipseCase, seg, branchNumber ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathExportMswCompletionsImpl::assignBranchNumbersToBranch( const RimEclipseCase* eclipseCase, RicMswExportInfo* exportInfo, gsl::not_null branch, gsl::not_null branchNumber ) { branch->setBranchNumber( *branchNumber ); // Assign perforations first to ensure the same branch number as the segment for ( auto segment : branch->segments() ) { assignBranchNumbersToPerforations( eclipseCase, segment, branchNumber ); } // Assign other completions with an incremented branch number for ( auto segment : branch->segments() ) { assignBranchNumbersToOtherCompletions( eclipseCase, segment, branchNumber ); } ( *branchNumber )++; for ( auto childBranch : branch->branches() ) { assignBranchNumbersToBranch( eclipseCase, exportInfo, childBranch, branchNumber ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::unique_ptr RicWellPathExportMswCompletionsImpl::createChildMswBranch( const RimWellPath* childWellPath ) { auto initialChildMD = childWellPath->wellPathTieIn()->tieInMeasuredDepth(); auto initialChildTVD = -childWellPath->wellPathGeometry()->interpolatedPointAlongWellPath( initialChildMD ).z(); const RimWellPathValve* outletValve = childWellPath->wellPathTieIn()->outletValve(); if ( outletValve ) { auto branchStartingWithValve = RicMswValve::createTieInValve( QString( "%1 valve for %2" ) .arg( outletValve->componentLabel() ) .arg( childWellPath->name() ), childWellPath, initialChildMD, initialChildTVD, outletValve ); if ( branchStartingWithValve ) { auto dummySegment = std::make_unique( QString( "%1 segment" ).arg( outletValve->componentLabel() ), initialChildMD, initialChildMD + 0.1, initialChildTVD, RicMswTableFormatterTools::tvdFromMeasuredDepth( childWellPath, initialChildMD + 0.1 ) ); branchStartingWithValve->addSegment( std::move( dummySegment ) ); return branchStartingWithValve; } } auto childBranch = std::make_unique( childWellPath->name(), childWellPath, initialChildMD, initialChildTVD ); return childBranch; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathExportMswCompletionsImpl::wellPathsWithTieIn( const RimWellPath* wellPath ) { std::vector connectedWellPaths; { auto wellPaths = RimProject::current()->allWellPaths(); for ( auto well : wellPaths ) { if ( well && well->isEnabled() && well->wellPathTieIn() && well->wellPathTieIn()->parentWell() == wellPath ) { connectedWellPaths.push_back( well ); } } } return connectedWellPaths; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RicWellPathExportMswCompletionsImpl::calculateOverlapWithActiveCells( double startMD, double endMD, const std::vector& wellPathIntersections, const RigActiveCellInfo* activeCellInfo ) { for ( const WellPathCellIntersectionInfo& intersection : wellPathIntersections ) { if ( intersection.globCellIndex < activeCellInfo->reservoirCellCount() && activeCellInfo->isActive( intersection.globCellIndex ) ) { double overlapStart = std::max( startMD, intersection.startMD ); double overlapEnd = std::min( endMD, intersection.endMD ); if ( overlapEnd > overlapStart ) { return std::make_pair( overlapStart, overlapEnd ); } } } return std::make_pair( 0.0, 0.0 ); }