ResInsight/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswCompletionsImpl.cpp
Magne Sjaastad 296718fd29
Fix location of ICV for MSW wells
* Show warning if no wells are visible
* #9461 MSW: Start export of a lateral at the tie in location on parent well
Previous implementation used the full well path for the lateral, including the part of the parent well up to sea level.
2022-12-14 08:59:24 +01:00

2074 lines
99 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// 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 <http://www.gnu.org/licenses/gpl.html>
// 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 <QFile>
#include <QFileInfo>
#include <algorithm>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicWellPathExportMswCompletionsImpl::exportWellSegmentsForAllCompletions(
const RicExportCompletionDataSettingsUi& exportSettings,
const std::vector<RimWellPath*>& wellPaths )
{
std::shared_ptr<QFile> unifiedExportFile;
std::shared_ptr<QFile> 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<QFile> unifiedWellPathFile;
std::shared_ptr<QFile> 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<QFile> exportFile;
std::shared_ptr<QFile> 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<QFile> exportFile;
std::shared_ptr<QFile> 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<QFile> exportFile;
std::shared_ptr<QFile> 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<QFile> exportFile,
std::shared_ptr<QFile> 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<QFile> exportFile,
std::shared_ptr<QFile> 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<QFile> exportFile,
std::shared_ptr<QFile> 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<RicMswBranch*> branch )
{
auto allSegments = branch->allSegmentsRecursively();
{
// Update effective diameter
// https://github.com/OPM/ResInsight/issues/7686
std::map<size_t, std::set<RicMswSegment*>> 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<size_t, std::set<RicMswFishbonesICD*>> icdsInCell;
{
for ( auto s : allSegments )
{
for ( auto completion : s->completions() )
{
if ( auto icd = dynamic_cast<RicMswFishbonesICD*>( 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<WellPathCellIntersectionInfo>& cellIntersections,
bool enableSegmentSplitting,
gsl::not_null<RicMswExportInfo*> exportInfo,
gsl::not_null<RicMswBranch*> branch )
{
std::vector<RimFishbones*> 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<WellPathCellIntersectionInfo>& cellIntersections,
const std::vector<RimFishbones*>& fishbonesSubs,
bool enableSegmentSplitting,
gsl::not_null<RicMswExportInfo*> exportInfo,
gsl::not_null<RicMswBranch*> branch )
{
std::vector<WellPathCellIntersectionInfo> 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<double>::infinity();
auto unitSystem = exportInfo->unitSystem();
for ( RimFishbones* subs : fishbonesSubs )
{
std::map<size_t, std::vector<size_t>> subAndLateralIndices;
for ( const auto& [subIndex, lateralIndex] : subs->installedLateralIndices() )
{
subAndLateralIndices[subIndex].push_back( lateralIndex );
}
// Find cell intersections closest to each sub location
std::map<size_t, std::vector<size_t>> closestSubForCellIntersections;
std::map<size_t, size_t> 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<double>::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<RicMswSegment>( "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<RicMswFishbones>( label, wellPath, subEndMD, subEndTVD, lateralIndex ) );
}
assignFishbonesLateralIntersections( eclipseCase,
branch->wellPath(),
subs,
icdSegment.get(),
&foundSubGridIntersections,
maxSegmentLength,
unitSystem );
auto icdCompletion =
std::make_unique<RicMswFishbonesICD>( 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<size_t> 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<RicMswSegmentCellIntersection>( 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 ) );
}
}
}
}
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<RicMswExportInfo*> exportInfo,
gsl::not_null<RicMswBranch*> 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<WellPathCellIntersectionInfo>& cellIntersections,
gsl::not_null<RicMswExportInfo*> exportInfo,
gsl::not_null<RicMswBranch*> branch )
{
auto mswParameters = wellPath->mswCompletionParameters();
auto fractures = wellPath->fractureCollection()->activeFractures();
std::vector<WellPathCellIntersectionInfo> 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<RigCompletionData> 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<WellPathCellIntersectionInfo>& cellIntersections,
gsl::not_null<RicMswExportInfo*> exportInfo,
gsl::not_null<RicMswBranch*> 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<WellPathCellIntersectionInfo> 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 );
// Start MD of child well path at the tie in location
const double tieInOnParentWellPath =
childWellPath->wellPathTieIn() ? childWellPath->wellPathTieIn()->tieInMeasuredDepth() : initialMD;
if ( generatePerforationsMswExportInfo( eclipseCase,
childWellPath,
timeStep,
tieInOnParentWellPath,
childCellIntersections,
exportInfo,
childMswBranch.get() ) )
{
branch->addChildBranch( std::move( childMswBranch ) );
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<WellPathCellIntersectionInfo>
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<cvf::Vec3d>& coords = wellPathGeometry->uniqueWellPathPoints();
const std::vector<double>& mds = wellPathGeometry->uniqueMeasuredDepths();
CVF_ASSERT( !coords.empty() && !mds.empty() );
const RigMainGrid* mainGrid = eclipseCase->mainGrid();
std::vector<WellPathCellIntersectionInfo> allIntersections =
RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( eclipseCase->eclipseCaseData(),
wellPath->name(),
coords,
mds );
if ( allIntersections.empty() ) return {};
std::vector<WellPathCellIntersectionInfo> continuousIntersections =
RigWellPathIntersectionTools::buildContinuousIntersections( allIntersections, mainGrid );
return continuousIntersections;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RicWellPathExportMswCompletionsImpl::computeIntitialMeasuredDepth(
const RimEclipseCase* eclipseCase,
const RimWellPath* wellPath,
const RimMswCompletionParameters* mswParameters,
const std::vector<WellPathCellIntersectionInfo>& 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<double>::infinity();
{
std::vector<const RimWellPathComponentInterface*> 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<WellPathCellIntersectionInfo>
RicWellPathExportMswCompletionsImpl::filterIntersections( const std::vector<WellPathCellIntersectionInfo>& intersections,
double initialMD,
gsl::not_null<const RigWellPath*> wellPathGeometry,
gsl::not_null<const RimEclipseCase*> eclipseCase )
{
std::vector<WellPathCellIntersectionInfo> 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<size_t>::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<RicMswBranch*> branch,
const std::vector<WellPathCellIntersectionInfo>& cellSegmentIntersections,
const std::vector<const RimPerforationInterval*>& 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<RicMswSegment>( 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<RicMswPerforation>( interval->name(), wellPath, overlapStart, overlapStartTVD );
std::vector<RigCompletionData> 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<RicMswBranch*> branch,
const std::vector<const RimPerforationInterval*>& perforationIntervals,
const RimWellPath* wellPath,
RiaDefines::EclipseUnitSystem unitSystem )
{
int nMainSegment = 0;
auto segments = branch->segments();
for ( auto segment : segments )
{
std::unique_ptr<RicMswPerforationICV> ICV;
std::unique_ptr<RicMswPerforationICD> superICD;
std::unique_ptr<RicMswPerforationAICD> superAICD;
double totalICDOverlap = 0.0;
double totalAICDOverlap = 0.0;
for ( const RimPerforationInterval* interval : perforationIntervals )
{
if ( !interval->isChecked() ) continue;
std::vector<const RimWellPathValve*> 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<double, double> 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<RicMswSegment>( "Valve segment",
exportStartMD,
exportEndMD,
exportStartTVD,
exportEndTVD );
superAICD = std::make_unique<RicMswPerforationAICD>( 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<RicMswSegment>( "Valve segment",
exportStartMD,
exportEndMD,
exportStartTVD,
exportEndTVD );
superICD = std::make_unique<RicMswPerforationICD>( 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<RicMswSegment>( "Valve segment",
exportStartMD,
exportEndMD,
exportStartTVD,
exportEndTVD );
ICV = std::make_unique<RicMswPerforationICV>( 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<RicMswSegment>( "Valve segment",
exportStartMD,
exportEndMD,
exportStartTVD,
exportEndTVD );
superICD =
std::make_unique<RicMswPerforationICD>( 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<RicMswSegment>( "Valve segment",
exportStartMD,
exportEndMD,
exportStartTVD,
exportEndTVD );
superAICD = std::make_unique<RicMswPerforationAICD>( 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<RicMswBranch*> branch,
const std::vector<const RimPerforationInterval*>& perforationIntervals,
const std::vector<WellPathCellIntersectionInfo>& wellPathIntersections,
const RigActiveCellInfo* activeCellInfo,
RiaDefines::EclipseUnitSystem unitSystem )
{
using ValveContributionMap = std::map<RicMswCompletion*, std::vector<const RimWellPathValve*>>;
ValveContributionMap assignedRegularValves;
std::map<RicMswSegment*, std::unique_ptr<RicMswValveAccumulator>> accumulators;
for ( auto segment : branch->segments() )
{
RicMswValve* superValve = nullptr;
for ( auto completion : segment->completions() )
{
auto valve = dynamic_cast<RicMswValve*>( completion );
if ( valve )
{
superValve = valve;
break;
}
}
if ( dynamic_cast<RicMswPerforationICD*>( superValve ) )
{
accumulators[segment] = std::make_unique<RicMswICDAccumulator>( superValve, unitSystem );
}
else if ( dynamic_cast<RicMswPerforationAICD*>( superValve ) )
{
accumulators[segment] = std::make_unique<RicMswAICDAccumulator>( superValve, unitSystem );
}
}
for ( const RimPerforationInterval* interval : perforationIntervals )
{
if ( !interval->isChecked() ) continue;
std::vector<const RimWellPathValve*> 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<RicMswBranch*> branch,
const std::vector<const RimPerforationInterval*>& perforationIntervals,
RiaDefines::EclipseUnitSystem unitSystem )
{
std::map<const RimWellPathValve*, RicMswPerforationICV*> icvCompletionMap;
for ( auto segment : branch->segments() )
{
for ( auto completion : segment->completions() )
{
auto icv = dynamic_cast<RicMswPerforationICV*>( completion );
if ( icv )
{
icvCompletionMap[icv->wellPathValve()] = icv;
}
}
}
for ( auto segment : branch->segments() )
{
std::vector<RicMswCompletion*> 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<const RimWellPathValve*> 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<double, double> 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<RicMswBranch*> branch )
{
for ( auto segment : branch->segments() )
{
RicMswCompletion* superValve = nullptr;
std::vector<RicMswCompletion*> 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<RicMswSegment>( 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<RicMswSegment*> 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<std::pair<cvf::Vec3d, double>> lateralCoordMDPairs =
fishbonesSubs->coordsAndMDForLateral( segment->subIndex(), completion->index() );
if ( lateralCoordMDPairs.empty() )
{
continue;
}
std::vector<cvf::Vec3d> lateralCoords;
std::vector<double> 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<WellPathCellIntersectionInfo> 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<RicMswSegment>( "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<RicMswSegmentCellIntersection>( 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<RigCompletionData>& completionData,
gsl::not_null<RicMswSegment*> segment,
bool* foundSubGridIntersections )
{
CVF_ASSERT( foundSubGridIntersections != nullptr );
double position = fracture->fractureMD();
double width = fracture->fractureTemplate()->computeFractureWidth( fracture );
auto fractureCompletion = std::make_unique<RicMswFracture>( 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<RicMswSegment>( "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<RicMswSegmentCellIntersection>( cell.lgrName(),
cell.globalCellIndex(),
localIJK,
cvf::Vec3d::ZERO );
subSegment->addIntersection( intersection );
}
fractureCompletion->addSegment( std::move( subSegment ) );
segment->addCompletion( std::move( fractureCompletion ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RigCompletionData> RicWellPathExportMswCompletionsImpl::generatePerforationIntersections(
gsl::not_null<const RimWellPath*> wellPath,
gsl::not_null<const RimPerforationInterval*> perforationInterval,
int timeStep,
gsl::not_null<const RimEclipseCase*> eclipseCase )
{
std::vector<RigCompletionData> 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<cvf::Vec3d>, std::vector<double>> perforationPointsAndMD =
wellPathGeometry->clippedPointSubset( perforationInterval->startMD(), perforationInterval->endMD() );
std::vector<WellPathCellIntersectionInfo> 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<RigCompletionData>& completionData,
gsl::not_null<RicMswCompletion*> perforationCompletion,
const WellPathCellIntersectionInfo& cellIntInfo,
double overlapStart,
double overlapEnd,
bool* foundSubGridIntersections )
{
size_t currCellId = cellIntInfo.globCellIndex;
auto subSegment = std::make_unique<RicMswSegment>( "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<RicMswSegmentCellIntersection>( cell.lgrName(),
cell.globalCellIndex(),
localIJK,
cellIntInfo.intersectionLengthsInCellCS );
subSegment->addIntersection( intersection );
}
perforationCompletion->addSegment( std::move( subSegment ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicWellPathExportMswCompletionsImpl::assignBranchNumbersToPerforations( const RimEclipseCase* eclipseCase,
gsl::not_null<RicMswSegment*> segment,
gsl::not_null<int*> branchNumber )
{
for ( auto completion : segment->completions() )
{
if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION )
{
completion->setBranchNumber( *branchNumber );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicWellPathExportMswCompletionsImpl::assignBranchNumbersToOtherCompletions( const RimEclipseCase* eclipseCase,
gsl::not_null<RicMswSegment*> segment,
gsl::not_null<int*> 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<RicMswBranch*> branch,
gsl::not_null<int*> 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<RicMswBranch> 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<RicMswSegment>( 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<RicMswBranch>( childWellPath->name(), childWellPath, initialChildMD, initialChildTVD );
return childBranch;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellPath*> RicWellPathExportMswCompletionsImpl::wellPathsWithTieIn( const RimWellPath* wellPath )
{
std::vector<RimWellPath*> 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<double, double> RicWellPathExportMswCompletionsImpl::calculateOverlapWithActiveCells(
double startMD,
double endMD,
const std::vector<WellPathCellIntersectionInfo>& 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 );
}