ResInsight/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableFormatterTools.cpp
2021-11-02 13:53:42 +01:00

1137 lines
50 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// 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 <http://www.gnu.org/licenses/gpl.html>
// 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 <cmath>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
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<double, AICD_NUM_PARAMS>& 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<double, AICD_NUM_PARAMS> 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<RifTextDataTableColumn> 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<RifTextDataTableColumn> 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<RicMswBranch*> branch,
gsl::not_null<int*> 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<RicMswTieInICV*>( 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<size_t> intersectedCells;
std::set<RigCompletionData::CompletionType> perforationTypes = { RigCompletionData::CompletionType::PERFORATION,
RigCompletionData::CompletionType::PERFORATION_ICD,
RigCompletionData::CompletionType::PERFORATION_ICV,
RigCompletionData::CompletionType::PERFORATION_AICD };
std::set<RigCompletionData::CompletionType> fishbonesTypes = { RigCompletionData::CompletionType::FISHBONES_ICD,
RigCompletionData::CompletionType::FISHBONES };
std::set<RigCompletionData::CompletionType> 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<const RicMswBranch*> branch,
bool exportSubGridIntersections,
const std::set<RigCompletionData::CompletionType>& exportCompletionTypes,
gsl::not_null<bool*> headerGenerated,
gsl::not_null<std::set<size_t>*> intersectedCells )
{
for ( auto segment : branch->segments() )
{
auto completion = dynamic_cast<const RicMswCompletion*>( 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<RifTextDataTableColumn> header = { RifTextDataTableColumn( "Name" ) };
formatter.header( header );
formatter.add( exportInfo.mainBoreBranch()->wellPath()->completionSettings()->wellNameForExport() );
formatter.rowCompleted();
}
{
std::vector<RifTextDataTableColumn> allHeaders;
if ( exportSubGridIntersections )
{
allHeaders.push_back( RifTextDataTableColumn( "Grid" ) );
}
std::vector<RifTextDataTableColumn> 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 )
{
bool foundValve = false;
std::map<size_t, std::vector<WsegvalveData>> 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<RicMswBranch*> branch,
const QString& wellNameForExport,
std::map<size_t, std::vector<WsegvalveData>>& wsegvalveData )
{
{
auto tieInValve = dynamic_cast<RicMswTieInICV*>( 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<size_t>::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<RicMswWsegValve*>( 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<size_t, std::vector<AicdWsegvalveData>> 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<QString> 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<RifTextDataTableColumn> 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<double, AICD_NUM_PARAMS> 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<const RicMswBranch*> branch,
std::map<size_t, std::vector<AicdWsegvalveData>>& aicdValveData )
{
for ( auto segment : branch->segments() )
{
for ( auto completion : segment->completions() )
{
if ( completion->completionType() == RigCompletionData::CompletionType::PERFORATION_AICD )
{
auto aicd = static_cast<const RicMswPerforationAICD*>( 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<RicMswBranch*> branch,
int* segmentNumber )
{
CVF_ASSERT( segment && segmentNumber );
double startMD = segment->startMD();
double endMD = segment->endMD();
std::vector<std::pair<double, double>> segments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength );
CVF_ASSERT( branch->wellPath() );
auto wellPathGeometry = branch->wellPath()->wellPathGeometry();
CVF_ASSERT( wellPathGeometry );
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<std::pair<double, double>> splitSegments = createSubSegmentMDPairs( startMD, endMD, maxSegmentLength );
auto wellPathGeometry = valve->wellPath()->wellPathGeometry();
CVF_ASSERT( wellPathGeometry );
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<const RicMswSegment*> outletSegment,
gsl::not_null<const RicMswCompletion*> 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<std::pair<double, double>> 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<RicMswSegment*>( completionSegment );
noConst->setSegmentNumber( outletSegmentNumber );
for ( auto comp : completionSegment->completions() )
{
writeCompletionWelsegsSegments( completionSegment, comp, formatter, exportInfo, maxSegmentLength, segmentNumber );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicMswTableFormatterTools::writeCompletionsForSegment( gsl::not_null<const RicMswSegment*> outletSegment,
gsl::not_null<RicMswSegment*> 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<RicMswPerforation*>( completion );
if ( performationMsw ) continue;
auto segmentValve = dynamic_cast<RicMswValve*>( completion );
auto fishboneIcd = dynamic_cast<RicMswFishbonesICD*>( 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<std::pair<double, double>>
RicMswTableFormatterTools::createSubSegmentMDPairs( double startMD, double endMD, double maxSegmentLength )
{
int subSegmentCount = (int)( std::trunc( ( endMD - startMD ) / maxSegmentLength ) + 1 );
double subSegmentLength = ( endMD - startMD ) / subSegmentCount;
std::vector<std::pair<double, double>> 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<const RimWellPath*> 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<RifTextDataTableColumn> header = {
RifTextDataTableColumn( "Well Name" ),
RifTextDataTableColumn( "Seg No" ),
RifTextDataTableColumn( "Cv" ),
RifTextDataTableColumn( "Ac" ),
};
formatter.header( header );
}