///////////////////////////////////////////////////////////////////////////////// // // 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 "RifTextDataTableFormatter.h" #include "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; }; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- class RicMswTableFormatterTools::AicdWsegvalveData { public: explicit AicdWsegvalveData( const QString& wellName, const QString& comment, int segmentNumber, double flowScalingFactor, bool isOpen, const std::array& values ) : m_wellName( wellName ) , m_comment( comment ) , m_segmentNumber( segmentNumber ) , m_flowScalingFactor( flowScalingFactor ) , m_isOpen( isOpen ) , m_values( values ) { } QString m_wellName; QString m_comment; int m_segmentNumber; double m_flowScalingFactor; bool m_isOpen; std::array m_values; }; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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() ); } cvf::Vec3st ijk = intersection->gridLocalCellIJK(); formatter.addOneBasedCellIndex( ijk.x() ).addOneBasedCellIndex( ijk.y() ).addOneBasedCellIndex( ijk.z() ); 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; generateWsegAicdTableRecursively( exportInfo, exportInfo.mainBoreBranch(), aicdValveData ); if ( !aicdValveData.empty() ) { RifTextDataTableFormatter tighterFormatter( formatter ); tighterFormatter.setColumnSpacing( 1 ); tighterFormatter.setTableRowPrependText( " " ); { // Write out header for AICD table std::vector columnDescriptions = { "Well Name", "Segment Number", "Segment Number", "Strength of AICD", "Flow Scaling Factor for AICD", "Density of Calibration Fluid", "Viscosity of Calibration Fluid", "Critical water in liquid fraction for emulsions viscosity model", "Emulsion viscosity transition region", "Max ratio of emulsion viscosity to continuous phase viscosity", "Flow scaling factor method", "Maximum flow rate for AICD device", "Volume flow rate exponent, x", "Viscosity function exponent, y", "Device OPEN/SHUT", "Exponent of the oil flowing fraction in the density mixture calculation", "Exponent of the water flowing fraction in the density mixture calculation", "Exponent of the gas flowing fraction in the density mixture calculation", "Exponent of the oil flowing fraction in the density viscosity calculation", "Exponent of the water flowing fraction in the density viscosity calculation", "Exponent of the gas flowing fraction in the density viscosity calculation" }; tighterFormatter.keyword( "WSEGAICD" ); tighterFormatter.comment( "Column Overview:" ); for ( size_t i = 0; i < columnDescriptions.size(); ++i ) { tighterFormatter.comment( QString( "%1: %2" ).arg( i + 1, 2, 10, QChar( '0' ) ).arg( columnDescriptions[i] ) ); } std::vector header; for ( size_t i = 1; i <= 21; ++i ) { QString cName = QString( "%1" ).arg( i, 2, 10, QChar( '0' ) ); RifTextDataTableColumn col( cName, RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_CONSISE ), RIGHT ); header.push_back( col ); } tighterFormatter.header( header ); } // 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 ) { tighterFormatter.comment( comment ); } auto firstDataObject = aicdDataForSameCell.front(); tighterFormatter.add( firstDataObject.m_wellName ); // #1 tighterFormatter.add( firstDataObject.m_segmentNumber ); tighterFormatter.add( firstDataObject.m_segmentNumber ); std::array values = firstDataObject.m_values; tighterFormatter.add( values[AICD_STRENGTH] ); double flowScalingFactor = 1.0 / accumulatedFlowScalingFactorDivisor; tighterFormatter.add( flowScalingFactor ); // #5 Eclipse Flow scaling factor used when // item #11 is set to '1' tighterFormatter.add( values[AICD_DENSITY_CALIB_FLUID] ); tighterFormatter.add( values[AICD_VISCOSITY_CALIB_FLUID] ); tighterFormatter.addValueOrDefaultMarker( values[AICD_CRITICAL_WATER_IN_LIQUID_FRAC], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.addValueOrDefaultMarker( values[AICD_EMULSION_VISC_TRANS_REGION], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.addValueOrDefaultMarker( values[AICD_MAX_RATIO_EMULSION_VISC], RicMswExportInfo::defaultDoubleValue() ); // #10 tighterFormatter.add( 1 ); // #11 : Always use method "b. Scale factor". The value of the // scale factor is given in item #5 tighterFormatter.addValueOrDefaultMarker( values[AICD_MAX_FLOW_RATE], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.add( values[AICD_VOL_FLOW_EXP] ); tighterFormatter.add( values[AICD_VISOSITY_FUNC_EXP] ); tighterFormatter.add( firstDataObject.m_isOpen ? "OPEN" : "SHUT" ); // #15 tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_OIL_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_WATER_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_GAS_FRAC_DENSITY], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_OIL_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_WATER_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() ); // #20 tighterFormatter.addValueOrDefaultMarker( values[AICD_EXP_GAS_FRAC_VISCOSITY], RicMswExportInfo::defaultDoubleValue() ); tighterFormatter.rowCompleted(); } tighterFormatter.tableCompleted(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicMswTableFormatterTools::generateWsegAicdTableRecursively( RicMswExportInfo& exportInfo, gsl::not_null branch, std::map>& aicdValveData ) { for ( auto segment : branch->segments() ) { for ( auto completion : segment->completions() ) { if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD ) { auto aicd = static_cast( completion ); if ( aicd->isValid() ) { int segmentNumber = -1; for ( auto seg : aicd->segments() ) { if ( seg->segmentNumber() > -1 ) segmentNumber = seg->segmentNumber(); if ( seg->intersections().empty() ) continue; size_t cellIndex = seg->intersections().front()->globalCellIndex(); auto wellName = exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport(); auto comment = aicd->label(); aicdValveData[cellIndex].push_back( AicdWsegvalveData( wellName, comment, segmentNumber, aicd->flowScalingFactor(), aicd->isOpen(), aicd->values() ) ); } } else { RiaLogging::error( QString( "Export AICD Valve (%1): Valve is invalid. At least one required " "template parameter is not set." ) .arg( aicd->label() ) ); } } } } for ( auto childBranch : branch->branches() ) { generateWsegAicdTableRecursively( exportInfo, childBranch, aicdValveData ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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(); } const auto linerDiameter = branch->wellPath()->mswCompletionParameters()->linerDiameter( exportInfo.unitSystem() ); const auto roughnessFactor = branch->wellPath()->mswCompletionParameters()->roughnessFactor( exportInfo.unitSystem() ); 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 ); 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 we have a valve, the outlet segment is the valve's segment RicMswSegment* outletSegment = *outletValve && ( *outletValve )->segmentCount() > 0 ? ( *outletValve )->segments().front() : segment.get(); writeCompletionWelsegsSegments( outletSegment, completion, formatter, exportInfo, maxSegmentLength, segmentNumber ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> RicMswTableFormatterTools::createSubSegmentMDPairs( double startMD, double endMD, double maxSegmentLength ) { int subSegmentCount = (int)( std::trunc( ( endMD - startMD ) / maxSegmentLength ) + 1 ); double subSegmentLength = ( endMD - startMD ) / subSegmentCount; std::vector> subSegmentMDPairs; 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( subSegmentLength, endMD ); } 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 ); }