///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2021 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 "RicMswTableFormatterTools.h" #include "RiaLogging.h" #include "RicMswCompletions.h" #include "RicMswExportInfo.h" #include "RicMswTableDataTools.h" #include "RifTextDataTableFormatter.h" #include "CompletionsMsw/RigMswDataFormatter.h" #include "Well/RigWellPath.h" #include "RimMswCompletionParameters.h" #include "RimWellPath.h" // TODO: Consider adding wellnameforexport to RicMswExportInfo to avoid these includes #include "RimWellPathCompletionSettings.h" #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- class RicMswTableFormatterTools::WsegvalveData { public: explicit WsegvalveData( const QString& wellName, const QString& comment, int segmentNumber, double cv, double ac ) : m_wellName( wellName ) , m_comment( comment ) , m_segmentNumber( segmentNumber ) , m_cv( cv ) , m_ac( ac ) { } QString m_wellName; QString m_comment; int m_segmentNumber; double m_cv; double m_ac; }; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateWelsegsTable( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, double maxSegmentLength, bool exportCompletionSegmentsAfterMainBore ) { formatter.keyword( "WELSEGS" ); double startMD = exportInfo.mainBoreBranch()->startMD(); double startTVD = exportInfo.mainBoreBranch()->startTVD(); { std::vector header = { RifTextDataTableColumn( "Name" ), RifTextDataTableColumn( "Dep 1" ), RifTextDataTableColumn( "Tlen 1" ), RifTextDataTableColumn( "Vol 1" ), RifTextDataTableColumn( "Len&Dep" ), RifTextDataTableColumn( "PresDrop" ), }; formatter.header( header ); formatter.add( exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport() ); formatter.add( startTVD ); formatter.add( startMD ); formatter.addValueOrDefaultMarker( exportInfo.topWellBoreVolume(), RicMswExportInfo::defaultDoubleValue() ); formatter.add( exportInfo.lengthAndDepthText() ); formatter.add( QString( "'%1'" ).arg( exportInfo.pressureDropText() ) ); formatter.rowCompleted(); } { std::vector header = { RifTextDataTableColumn( "First Seg" ), RifTextDataTableColumn( "Last Seg" ), RifTextDataTableColumn( "Branch Num" ), RifTextDataTableColumn( "Outlet Seg" ), RifTextDataTableColumn( "Length" ), RifTextDataTableColumn( "Depth Change" ), RifTextDataTableColumn( "Diam" ), RifTextDataTableColumn( "Rough", RifTextDataTableDoubleFormatting( RIF_FLOAT, 7 ) ) }; formatter.header( header ); } int segmentNumber = 2; // There's an implicit segment number 1. RicMswSegment* parentSegment = nullptr; writeWelsegsSegmentsRecursively( formatter, exportInfo, exportInfo.mainBoreBranch(), &segmentNumber, maxSegmentLength, exportCompletionSegmentsAfterMainBore, parentSegment ); formatter.tableCompleted(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeWelsegsSegmentsRecursively( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, gsl::not_null branch, gsl::not_null segmentNumber, double maxSegmentLength, bool exportCompletionSegmentsAfterMainBore, RicMswSegment* connectedToSegment ) { auto outletSegment = connectedToSegment; RicMswValve* outletValve = nullptr; auto branchSegments = branch->segments(); auto it = branchSegments.begin(); if ( outletValve = dynamic_cast( branch.get() ); outletValve != nullptr ) { writeValveWelsegsSegment( outletSegment, outletValve, formatter, exportInfo, maxSegmentLength, segmentNumber ); auto valveSegments = outletValve->segments(); outletSegment = valveSegments.front(); *segmentNumber = outletSegment->segmentNumber() + 1; ++it; // skip segment below } formatter.addOptionalComment( QString( "Segments on branch %1" ).arg( branch->label() ) ); auto branchStartSegmentIterator = it; for ( ; it != branchSegments.end(); ++it ) { auto segment = *it; segment->setSegmentNumber( *segmentNumber ); if ( segment->subIndex() != cvf::UNDEFINED_SIZE_T ) { QString comment = segment->label() + QString( ", sub %1" ).arg( segment->subIndex() + 1 ); formatter.addOptionalComment( comment ); } writeWelsegsSegment( segment, outletSegment, formatter, exportInfo, maxSegmentLength, branch, segmentNumber ); outletSegment = segment; if ( !exportCompletionSegmentsAfterMainBore ) { writeCompletionsForSegment( outletSegment, segment, &outletValve, formatter, exportInfo, maxSegmentLength, segmentNumber ); } } if ( exportCompletionSegmentsAfterMainBore ) { it = branchStartSegmentIterator; for ( ; it != branchSegments.end(); ++it ) { auto segment = *it; writeCompletionsForSegment( outletSegment, segment, &outletValve, formatter, exportInfo, maxSegmentLength, segmentNumber ); } } for ( auto childBranch : branch->branches() ) { RicMswSegment* outletSegmentForChildBranch = outletSegment; RicMswSegment* tieInSegmentOnParentBranch = branch->findClosestSegmentWithLowerMD( childBranch->startMD() ); if ( tieInSegmentOnParentBranch ) outletSegmentForChildBranch = tieInSegmentOnParentBranch; writeWelsegsSegmentsRecursively( formatter, exportInfo, childBranch, segmentNumber, maxSegmentLength, exportCompletionSegmentsAfterMainBore, outletSegmentForChildBranch ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeWelsegsCompletionCommentHeader( RifTextDataTableFormatter& formatter, RigCompletionData::CompletionType completionType ) { QString optionalCommentText; switch ( completionType ) { case RigCompletionData::CompletionType::FISHBONES: break; case RigCompletionData::CompletionType::FRACTURE: optionalCommentText = "Fracture Segments"; break; case RigCompletionData::CompletionType::PERFORATION: optionalCommentText = "Perforation Segments"; break; case RigCompletionData::CompletionType::FISHBONES_ICD: optionalCommentText = "Fishbones Segments - ICD"; break; case RigCompletionData::CompletionType::PERFORATION_ICD: optionalCommentText = "Perforation Segments - ICD"; break; case RigCompletionData::CompletionType::PERFORATION_AICD: optionalCommentText = "Perforation Segments - AICD"; break; case RigCompletionData::CompletionType::PERFORATION_ICV: optionalCommentText = "Perforation Segments - ICV"; break; case RigCompletionData::CompletionType::CT_UNDEFINED: optionalCommentText = "Main Stem"; break; default: break; } if ( !optionalCommentText.isEmpty() ) { formatter.addOptionalComment( optionalCommentText ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateCompsegTables( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, bool exportLgrData ) { /* * TODO: Creating the regular perforation COMPSEGS table should come in here, before the others * should take precedence by appearing later in the output. See #3230. */ std::set intersectedCells; std::set perforationTypes = { RigCompletionData::CompletionType::PERFORATION, RigCompletionData::CompletionType::PERFORATION_ICD, RigCompletionData::CompletionType::PERFORATION_ICV, RigCompletionData::CompletionType::PERFORATION_AICD }; std::set fishbonesTypes = { RigCompletionData::CompletionType::FISHBONES_ICD, RigCompletionData::CompletionType::FISHBONES }; std::set fractureTypes = { RigCompletionData::CompletionType::FRACTURE }; { bool headerGenerated = false; generateCompsegTable( formatter, exportInfo, exportInfo.mainBoreBranch(), exportLgrData, perforationTypes, &headerGenerated, &intersectedCells ); generateCompsegTable( formatter, exportInfo, exportInfo.mainBoreBranch(), exportLgrData, fishbonesTypes, &headerGenerated, &intersectedCells ); generateCompsegTable( formatter, exportInfo, exportInfo.mainBoreBranch(), exportLgrData, fractureTypes, &headerGenerated, &intersectedCells ); if ( headerGenerated ) formatter.tableCompleted(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateCompsegTable( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, gsl::not_null branch, bool exportSubGridIntersections, const std::set& exportCompletionTypes, gsl::not_null headerGenerated, gsl::not_null*> intersectedCells ) { for ( auto segment : branch->segments() ) { auto completion = dynamic_cast( branch.get() ); for ( auto intersection : segment->intersections() ) { bool isSubGridIntersection = !intersection->gridName().isEmpty(); if ( isSubGridIntersection != exportSubGridIntersections ) continue; double startLength = segment->startMD(); double endLength = segment->endMD(); if ( completion ) { bool isPerforationValve = completion->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD || completion->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD || completion->completionType() == RigCompletionData::CompletionType::PERFORATION_ICV; if ( isPerforationValve ) { startLength = segment->startMD(); endLength = segment->endMD(); } } size_t globalCellIndex = intersection->globalCellIndex(); // Here we check if the cell is already reported. Make sure we report intersections before other completions // on the segment to be able to connect the branch with most flow if ( !intersectedCells->count( globalCellIndex ) ) { if ( exportSubGridIntersections ) { formatter.add( intersection->gridName() ); } auto ijk = intersection->gridLocalCellIJK().toOneBased(); formatter.add( ijk.i() ).add( ijk.j() ).add( ijk.k() ); int branchNumber = -1; if ( completion ) branchNumber = completion->branchNumber(); formatter.add( branchNumber ); formatter.add( startLength ); formatter.add( endLength ); formatter.rowCompleted(); intersectedCells->insert( globalCellIndex ); } } // Report connected completions after the intersection on current segment has been reported for ( auto completion : segment->completions() ) { if ( completion->segments().empty() || !exportCompletionTypes.count( completion->completionType() ) ) continue; if ( !*headerGenerated ) { generateCompsegHeader( formatter, exportInfo, completion->completionType(), exportSubGridIntersections ); *headerGenerated = true; } generateCompsegTable( formatter, exportInfo, completion, exportSubGridIntersections, exportCompletionTypes, headerGenerated, intersectedCells ); } } for ( auto childBranch : branch->branches() ) { generateCompsegTable( formatter, exportInfo, childBranch, exportSubGridIntersections, exportCompletionTypes, headerGenerated, intersectedCells ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateCompsegHeader( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, RigCompletionData::CompletionType completionType, bool exportSubGridIntersections ) { if ( exportSubGridIntersections ) { formatter.keyword( "COMPSEGL" ); } else { formatter.keyword( "COMPSEGS" ); } if ( completionType == RigCompletionData::CompletionType::FISHBONES_ICD ) { formatter.comment( "Fishbones" ); } else if ( completionType == RigCompletionData::CompletionType::FRACTURE ) { formatter.comment( "Fractures" ); } { std::vector header = { RifTextDataTableColumn( "Name" ) }; formatter.header( header ); formatter.add( exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport() ); formatter.rowCompleted(); } { std::vector allHeaders; if ( exportSubGridIntersections ) { allHeaders.push_back( RifTextDataTableColumn( "Grid" ) ); } std::vector commonHeaders = { RifTextDataTableColumn( "I" ), RifTextDataTableColumn( "J" ), RifTextDataTableColumn( "K" ), RifTextDataTableColumn( "Branch no" ), RifTextDataTableColumn( "Start Length" ), RifTextDataTableColumn( "End Length" ), RifTextDataTableColumn( "Dir Pen" ), RifTextDataTableColumn( "End Range" ), RifTextDataTableColumn( "Connection Depth" ) }; allHeaders.insert( allHeaders.end(), commonHeaders.begin(), commonHeaders.end() ); formatter.header( allHeaders ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateWsegvalvTable( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo ) { std::map> wsegvalveData; generateWsegvalvTableRecursively( exportInfo.mainBoreBranch(), exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport(), wsegvalveData ); if ( !wsegvalveData.empty() ) { writeWsegvalHeader( formatter ); for ( auto [globalCellIndex, dataForSameGridCell] : wsegvalveData ) { if ( dataForSameGridCell.empty() ) continue; double combinedFlowArea = 0.0; for ( const auto& cellData : dataForSameGridCell ) { combinedFlowArea += cellData.m_ac; } auto firstDataObject = dataForSameGridCell.front(); formatter.add( firstDataObject.m_wellName ); formatter.add( firstDataObject.m_segmentNumber ); formatter.add( firstDataObject.m_cv ); formatter.add( QString( "%1" ).arg( combinedFlowArea, 8, 'g', 4 ) ); formatter.rowCompleted(); } formatter.tableCompleted(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateWsegvalvTableRecursively( gsl::not_null branch, const QString& wellNameForExport, std::map>& wsegvalveData ) { { auto tieInValve = dynamic_cast( branch.get() ); if ( tieInValve && !tieInValve->segments().empty() ) { auto firstSubSegment = tieInValve->segments().front(); CAF_ASSERT( tieInValve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICV ); size_t cellIndex = std::numeric_limits::max(); if ( !firstSubSegment->intersections().empty() ) { cellIndex = firstSubSegment->intersections().front()->globalCellIndex(); } auto flowCoefficient = tieInValve->flowCoefficient(); wsegvalveData[cellIndex].push_back( WsegvalveData( wellNameForExport, tieInValve->label(), firstSubSegment->segmentNumber(), flowCoefficient, tieInValve->area() ) ); } } for ( auto segment : branch->segments() ) { for ( auto completion : segment->completions() ) { if ( RigCompletionData::isWsegValveTypes( completion->completionType() ) ) { // Related function RicWellPathExportMswCompletionsImpl::moveIntersectionsToSuperICDsOrAICDs auto wsegValve = static_cast( completion ); int segmentNumber = -1; for ( auto seg : wsegValve->segments() ) { if ( seg->segmentNumber() > -1 ) segmentNumber = seg->segmentNumber(); if ( seg->intersections().empty() ) continue; size_t cellIndex = seg->intersections().front()->globalCellIndex(); QString comment; if ( wsegValve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD || wsegValve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICV ) { comment = wsegValve->label(); } wsegvalveData[cellIndex].push_back( WsegvalveData( wellNameForExport, comment, segmentNumber, wsegValve->flowCoefficient(), wsegValve->area() ) ); } } } } for ( auto childBranch : branch->branches() ) { generateWsegvalvTableRecursively( childBranch, wellNameForExport, wsegvalveData ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateWsegAicdTable( RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo ) { std::map> aicdValveData; RicMswTableDataTools::generateWsegAicdTableRecursively( exportInfo, exportInfo.mainBoreBranch(), aicdValveData ); if ( !aicdValveData.empty() ) { RifTextDataTableFormatter tightFormatter( formatter ); tightFormatter.setColumnSpacing( 1 ); tightFormatter.setTableRowPrependText( " " ); RigMswDataFormatter::addWsegaicdHeader( tightFormatter ); // Export data for each cell with AICD valves for ( auto [globalCellIndex, aicdDataForSameCell] : aicdValveData ) { if ( aicdDataForSameCell.empty() ) continue; double accumulatedFlowScalingFactorDivisor = 0.0; QStringList comments; // See RicMswAICDAccumulator::accumulateValveParameters for similar accumulation for multiple valves in same // segment for ( const auto& aicdData : aicdDataForSameCell ) { accumulatedFlowScalingFactorDivisor += 1.0 / aicdData.m_flowScalingFactor; comments.push_back( aicdData.m_comment ); } for ( auto comment : comments ) { tightFormatter.comment( comment ); } auto firstDataObject = aicdDataForSameCell.front(); tightFormatter.add( firstDataObject.m_wellName ); // #1 tightFormatter.add( firstDataObject.m_segmentNumber ); tightFormatter.add( firstDataObject.m_segmentNumber ); std::array values = firstDataObject.m_values; tightFormatter.add( values[AICD_STRENGTH] ); double flowScalingFactor = 1.0 / accumulatedFlowScalingFactorDivisor; tightFormatter.add( flowScalingFactor ); // #5 Eclipse Flow scaling factor used when // item #11 is set to '1' tightFormatter.add( values[AICD_DENSITY_CALIB_FLUID] ); tightFormatter.add( values[AICD_VISCOSITY_CALIB_FLUID] ); tightFormatter.addValueOrDefaultMarker( values[AICD_CRITICAL_WATER_IN_LIQUID_FRAC], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.addValueOrDefaultMarker( values[AICD_EMULSION_VISC_TRANS_REGION], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.addValueOrDefaultMarker( values[AICD_MAX_RATIO_EMULSION_VISC], RicMswExportInfo::defaultDoubleValue() ); // #10 tightFormatter.add( 1 ); // #11 : Always use method "b. Scale factor". The value of the // scale factor is given in item #5 tightFormatter.addValueOrDefaultMarker( values[AICD_MAX_FLOW_RATE], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.add( values[AICD_VOL_FLOW_EXP] ); tightFormatter.add( values[AICD_VISOSITY_FUNC_EXP] ); tightFormatter.add( firstDataObject.m_isOpen ? "OPEN" : "SHUT" ); // #15 tightFormatter.addValueOrDefaultMarker( values[AICD_EXP_OIL_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.addValueOrDefaultMarker( values[AICD_EXP_WATER_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.addValueOrDefaultMarker( values[AICD_EXP_GAS_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.addValueOrDefaultMarker( values[AICD_EXP_OIL_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.addValueOrDefaultMarker( values[AICD_EXP_WATER_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() ); // #20 tightFormatter.addValueOrDefaultMarker( values[AICD_EXP_GAS_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() ); tightFormatter.rowCompleted(); } tightFormatter.tableCompleted(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeWelsegsSegment( RicMswSegment* segment, const RicMswSegment* previousSegment, RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, double maxSegmentLength, gsl::not_null branch, int* segmentNumber ) { CVF_ASSERT( segment && segmentNumber ); double startMD = segment->startMD(); double endMD = segment->endMD(); std::vector> segments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength ); CVF_ASSERT( branch->wellPath() ); double prevOutMD = branch->startMD(); double prevOutTVD = branch->startTVD(); if ( previousSegment ) { prevOutMD = previousSegment->outputMD(); prevOutTVD = previousSegment->outputTVD(); } auto outletSegment = previousSegment; for ( const auto& [subStartMD, subEndMD] : segments ) { double depth = 0; double length = 0; double midPointMD = 0.5 * ( subStartMD + subEndMD ); double midPointTVD = tvdFromMeasuredDepth( branch->wellPath(), midPointMD ); if ( midPointMD < prevOutMD ) { // The first segment of parent branch may sometimes have a MD that is larger than the first segment on the // lateral. If this is the case, use the startMD of the branch instead prevOutMD = branch->startMD(); prevOutTVD = branch->startTVD(); } if ( exportInfo.lengthAndDepthText() == QString( "INC" ) ) { depth = midPointTVD - prevOutTVD; length = midPointMD - prevOutMD; } else { depth = midPointTVD; length = midPointMD; } segment->setOutputMD( midPointMD ); segment->setOutputTVD( midPointTVD ); segment->setSegmentNumber( *segmentNumber ); const auto linerDiameter = branch->wellPath()->mswCompletionParameters()->getDiameterAtMD( midPointMD, exportInfo.unitSystem() ); const auto roughnessFactor = branch->wellPath()->mswCompletionParameters()->getRoughnessAtMD( midPointMD, exportInfo.unitSystem() ); formatter.add( *segmentNumber ).add( *segmentNumber ); formatter.add( branch->branchNumber() ); if ( outletSegment ) formatter.add( outletSegment->segmentNumber() ); else formatter.add( 1 ); formatter.add( length ); formatter.add( depth ); formatter.add( linerDiameter ); formatter.add( roughnessFactor ); formatter.rowCompleted(); ( *segmentNumber )++; outletSegment = segment; prevOutMD = outletSegment->outputMD(); prevOutTVD = outletSegment->outputTVD(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeValveWelsegsSegment( const RicMswSegment* outletSegment, RicMswValve* valve, RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, double maxSegmentLength, int* segmentNumber ) { CVF_ASSERT( valve ); if ( !valve->isValid() ) return; CVF_ASSERT( !valve->label().isEmpty() ); CVF_ASSERT( valve->wellPath() ); formatter.addOptionalComment( valve->label() ); auto segments = valve->segments(); double startMD = 0.0; double endMD = 0.0; if ( valve->completionType() == RigCompletionData::CompletionType::PERFORATION_ICD || valve->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD ) { CVF_ASSERT( segments.size() > 1 ); // The 0.1 valve segment is the first, the perforated segment is the second auto subSegment = segments[0]; subSegment->setSegmentNumber( *segmentNumber ); double midPointMD = subSegment->outputMD(); startMD = midPointMD; endMD = startMD + 0.1; double midPointTVD = tvdFromMeasuredDepth( valve->wellPath(), midPointMD ); subSegment->setOutputMD( midPointMD ); subSegment->setOutputTVD( midPointTVD ); } else { auto subSegment = segments.front(); subSegment->setSegmentNumber( *segmentNumber ); startMD = subSegment->startMD(); endMD = subSegment->endMD(); double midPointMD = 0.5 * ( startMD + endMD ); double midPointTVD = tvdFromMeasuredDepth( valve->wellPath(), midPointMD ); subSegment->setOutputMD( midPointMD ); subSegment->setOutputTVD( midPointTVD ); } std::vector> splitSegments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength ); const auto linerDiameter = valve->wellPath()->mswCompletionParameters()->linerDiameter( exportInfo.unitSystem() ); const auto roughnessFactor = valve->wellPath()->mswCompletionParameters()->roughnessFactor( exportInfo.unitSystem() ); for ( const auto& [subStartMD, subEndMD] : splitSegments ) { int subSegmentNumber = ( *segmentNumber )++; double subStartTVD = tvdFromMeasuredDepth( valve->wellPath(), subStartMD ); double subEndTVD = tvdFromMeasuredDepth( valve->wellPath(), subEndMD ); double depth = 0; double length = 0; if ( exportInfo.lengthAndDepthText() == QString( "INC" ) ) { depth = subEndTVD - subStartTVD; length = subEndMD - subStartMD; } else { depth = subEndTVD; length = subEndMD; } formatter.add( subSegmentNumber ); formatter.add( subSegmentNumber ); formatter.add( valve->branchNumber() ); formatter.add( outletSegment->segmentNumber() ); formatter.add( length ); formatter.add( depth ); formatter.add( linerDiameter ); formatter.add( roughnessFactor ); formatter.rowCompleted(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeCompletionWelsegsSegments( gsl::not_null outletSegment, gsl::not_null completion, RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, double maxSegmentLength, int* segmentNumber ) { writeWelsegsCompletionCommentHeader( formatter, completion->completionType() ); if ( completion->completionType() == RigCompletionData::CompletionType::FISHBONES ) { formatter.addOptionalComment( QString( "Sub index %1 - %2" ).arg( outletSegment->subIndex() + 1 ).arg( completion->label() ) ); } else if ( completion->completionType() == RigCompletionData::CompletionType::FRACTURE ) { formatter.addOptionalComment( QString( "%1 connected to segment %2" ).arg( completion->label() ).arg( outletSegment->segmentNumber() ) ); } CVF_ASSERT( completion->wellPath() ); int outletSegmentNumber = outletSegment->segmentNumber(); for ( auto segment : completion->segments() ) { double startMD = segment->startMD(); double endMD = segment->endMD(); double startTVD = segment->startTVD(); double endTVD = segment->endTVD(); std::vector> splitSegments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength ); for ( const auto& [subStartMD, subEndMD] : splitSegments ) { int subSegmentNumber = ( *segmentNumber )++; // TODO: Verify this calculation for fractures double subStartTVD = tvdFromMeasuredDepth( completion->wellPath(), subStartMD ); double subEndTVD = tvdFromMeasuredDepth( completion->wellPath(), subEndMD ); if ( completion->completionType() == RigCompletionData::CompletionType::FISHBONES ) { // Not possible to do interpolation based on well path geometry here // Use linear interpolation based on start/end TVD for segment { auto normalizedWeight = ( subStartMD - startMD ) / ( endMD - startMD ); subStartTVD = startTVD * ( 1.0 - normalizedWeight ) + endTVD * normalizedWeight; } { auto normalizedWeight = ( subEndMD - startMD ) / ( endMD - startMD ); subEndTVD = startTVD * ( 1.0 - normalizedWeight ) + endTVD * normalizedWeight; } } double depth = 0; double length = 0; if ( exportInfo.lengthAndDepthText() == QString( "INC" ) ) { depth = subEndTVD - subStartTVD; length = subEndMD - subStartMD; } else { depth = subEndTVD; length = subEndMD; } double diameter = segment->equivalentDiameter(); if ( segment->effectiveDiameter() > 0.0 ) diameter = segment->effectiveDiameter(); formatter.add( subSegmentNumber ); formatter.add( subSegmentNumber ); formatter.add( completion->branchNumber() ); formatter.add( outletSegmentNumber ); formatter.add( length ); formatter.add( depth ); formatter.add( diameter ); formatter.add( segment->openHoleRoughnessFactor() ); formatter.rowCompleted(); outletSegmentNumber = subSegmentNumber; } for ( auto completionSegment : completion->segments() ) { auto noConst = const_cast( completionSegment ); noConst->setSegmentNumber( outletSegmentNumber ); for ( auto comp : completionSegment->completions() ) { writeCompletionWelsegsSegments( completionSegment, comp, formatter, exportInfo, maxSegmentLength, segmentNumber ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeCompletionsForSegment( gsl::not_null outletSegment, gsl::not_null segment, RicMswValve** outletValve, RifTextDataTableFormatter& formatter, RicMswExportInfo& exportInfo, double maxSegmentLength, int* segmentNumber ) { for ( auto& completion : segment->completions() ) { // For a well with perforation intervals, the WELSEGS segments are reported twice if if we include the // RicMswPerforation completions. Investigate when this class is intended to be exported to file auto performationMsw = dynamic_cast( completion ); if ( performationMsw ) continue; auto segmentValve = dynamic_cast( completion ); auto fishboneIcd = dynamic_cast( completion ); if ( !fishboneIcd && segmentValve != nullptr ) { writeValveWelsegsSegment( segment, segmentValve, formatter, exportInfo, maxSegmentLength, segmentNumber ); *outletValve = segmentValve; } else if ( dynamic_cast( completion ) ) { // Special handling for Tie-in ICVs RicMswSegment* outletSegment = *outletValve && ( *outletValve )->segmentCount() > 0 ? ( *outletValve )->segments().front() : segment.get(); writeCompletionWelsegsSegments( outletSegment, completion, formatter, exportInfo, maxSegmentLength, segmentNumber ); } else { // This is the default case for completions that are not valves writeCompletionWelsegsSegments( segment, completion, formatter, exportInfo, maxSegmentLength, segmentNumber ); } } } //-------------------------------------------------------------------------------------------------- /// Creates sub-segment MD pairs by combining custom intervals with max segment length subdivision /// Custom intervals define exact segment boundaries where specified /// Areas without custom intervals use max segment length subdivision (if maxSegmentLength > 0) //-------------------------------------------------------------------------------------------------- std::vector> RicMswTableFormatterTools::createSubSegmentMDPairs( double startMD, double endMD, double maxSegmentLength, const std::vector>& customSegmentIntervals ) { std::vector> subSegmentMDPairs; // If no custom intervals, use original logic with maxSegmentLength subdivision if ( customSegmentIntervals.empty() || maxSegmentLength <= 0.0 ) { int subSegmentCount = maxSegmentLength > 0.0 ? (int)( std::trunc( ( endMD - startMD ) / maxSegmentLength ) + 1 ) : 1; double subSegmentLength = ( endMD - startMD ) / subSegmentCount; double subStartMD = startMD; double subEndMD = startMD + subSegmentLength; for ( int i = 0; i < subSegmentCount; ++i ) { subSegmentMDPairs.push_back( std::make_pair( subStartMD, subEndMD ) ); subStartMD += subSegmentLength; subEndMD = std::min( subEndMD + subSegmentLength, endMD ); } return subSegmentMDPairs; } // Combine custom intervals with maxSegmentLength subdivision // Collect all boundaries (start, end, custom interval boundaries) and sort them std::set boundaries; boundaries.insert( startMD ); boundaries.insert( endMD ); // Add custom interval boundaries that overlap with [startMD, endMD] for ( const auto& [customStart, customEnd] : customSegmentIntervals ) { // Check if custom interval overlaps with [startMD, endMD] if ( customEnd > startMD && customStart < endMD ) { // Clip custom interval to [startMD, endMD] range double clippedStart = std::max( customStart, startMD ); double clippedEnd = std::min( customEnd, endMD ); if ( clippedStart < clippedEnd ) { boundaries.insert( clippedStart ); boundaries.insert( clippedEnd ); } } } // Convert boundaries to sorted vector std::vector sortedBoundaries( boundaries.begin(), boundaries.end() ); // For each gap between boundaries, either: // - Use exact boundary if it's from a custom interval // - Subdivide using maxSegmentLength if it's a gap for ( size_t i = 0; i + 1 < sortedBoundaries.size(); ++i ) { double gapStart = sortedBoundaries[i]; double gapEnd = sortedBoundaries[i + 1]; // Check if this gap is covered by a custom interval bool coveredByCustomInterval = false; for ( const auto& [customStart, customEnd] : customSegmentIntervals ) { double clippedStart = std::max( customStart, startMD ); double clippedEnd = std::min( customEnd, endMD ); // If the gap is fully within a custom interval, use exact boundaries if ( gapStart >= clippedStart && gapEnd <= clippedEnd && std::abs( gapStart - clippedStart ) < 1e-6 && std::abs( gapEnd - clippedEnd ) < 1e-6 ) { coveredByCustomInterval = true; subSegmentMDPairs.push_back( std::make_pair( gapStart, gapEnd ) ); break; } } // If not covered by custom interval, subdivide using maxSegmentLength if ( !coveredByCustomInterval && maxSegmentLength > 0.0 ) { double gapLength = gapEnd - gapStart; int subCount = (int)( std::trunc( gapLength / maxSegmentLength ) + 1 ); double subLength = gapLength / subCount; double subStart = gapStart; for ( int j = 0; j < subCount; ++j ) { double subEnd = ( j == subCount - 1 ) ? gapEnd : subStart + subLength; subSegmentMDPairs.push_back( std::make_pair( subStart, subEnd ) ); subStart = subEnd; } } else if ( !coveredByCustomInterval ) { // No maxSegmentLength, use gap as-is subSegmentMDPairs.push_back( std::make_pair( gapStart, gapEnd ) ); } } return subSegmentMDPairs; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RicMswTableFormatterTools::tvdFromMeasuredDepth( gsl::not_null wellPath, double measuredDepth ) { auto wellPathGeometry = wellPath->wellPathGeometry(); CVF_ASSERT( wellPathGeometry ); double tvdValue = -wellPathGeometry->interpolatedPointAlongWellPath( measuredDepth ).z(); return tvdValue; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::writeWsegvalHeader( RifTextDataTableFormatter& formatter ) { formatter.keyword( "WSEGVALV" ); std::vector header = { RifTextDataTableColumn( "Well Name" ), RifTextDataTableColumn( "Seg No" ), RifTextDataTableColumn( "Cv" ), RifTextDataTableColumn( "Ac" ), }; formatter.header( header ); }