///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2018- Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RicWellPathFractureTextReportFeatureImpl.h" #include "RiaRegressionTestRunner.h" #include "RicExportFractureCompletionsImpl.h" #include "RicWellPathFractureReportItem.h" #include "RifTextDataTableFormatter.h" #include "RigCompletionData.h" #include "RigTransmissibilityEquations.h" #include "RimEclipseCase.h" #include "RimEllipseFractureTemplate.h" #include "RimFileWellPath.h" #include "RimFractureContainment.h" #include "RimFractureTemplate.h" #include "RimFractureTemplateCollection.h" #include "RimOilField.h" #include "RimProject.h" #include "RimStimPlanFractureTemplate.h" #include "RimTools.h" #include "RimWellPath.h" #include "RimWellPathCollection.h" #include "RimWellPathCompletions.h" #include "RimWellPathFracture.h" #include "RimWellPathFractureCollection.h" //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString orientationText( RimFractureTemplate::FracOrientationEnum orientation ) { return caf::AppEnum::uiText( orientation ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RifTextDataTableColumn floatNumberColumn( const QString& text ) { return RifTextDataTableColumn( text, RifTextDataTableDoubleFormatting( RIF_FLOAT, 3 ), RIGHT ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::wellPathFractureReport( RimEclipseCase* sourceCase, const std::vector& wellPaths, const std::vector& wellPathFractureReportItems ) const { QString lineStart = "--"; QString text; QTextStream textStream( &text ); textStream << lineStart << "========================================================================================================\n"; textStream << lineStart << " RESINSIGHT DATA\n"; textStream << lineStart << "\n"; std::vector stimPlanTemplates; std::vector ellipseTemplates; { auto proj = RimProject::current(); auto fractureTemplates = proj->activeOilField()->fractureDefinitionCollection()->fractureTemplates(); std::set usedFractureTemplateNames; for ( const auto& item : wellPathFractureReportItems ) { usedFractureTemplateNames.insert( item.fractureTemplateName() ); } for ( const auto fracTemplate : fractureTemplates ) { if ( usedFractureTemplateNames.find( fracTemplate->name() ) == usedFractureTemplateNames.end() ) { continue; } auto stimPlanTemplate = dynamic_cast( fracTemplate ); if ( stimPlanTemplate ) { stimPlanTemplates.push_back( stimPlanTemplate ); } auto ellipseTemplate = dynamic_cast( fracTemplate ); if ( ellipseTemplate ) { ellipseTemplates.push_back( ellipseTemplate ); } } } if ( !RiaRegressionTestRunner::instance()->isRunningRegressionTests() ) { if ( sourceCase ) { textStream << lineStart << " Grid Model:\n"; textStream << lineStart << " " << sourceCase->gridFileName() << "\n"; textStream << lineStart << "\n"; } { QString tableText = createWellFileLocationText( wellPaths ); textStream << tableText; textStream << lineStart << "\n"; } { QString tableText = createStimPlanFileLocationText( stimPlanTemplates ); textStream << tableText; textStream << lineStart << "\n"; } } { QString tableText = createEllipseFractureText( ellipseTemplates ); textStream << tableText; textStream << lineStart << "\n"; } { QString tableText = createStimPlanFractureText( stimPlanTemplates ); textStream << tableText; textStream << lineStart << "\n"; } { std::vector fracTemplates; fracTemplates.insert( fracTemplates.end(), ellipseTemplates.begin(), ellipseTemplates.end() ); fracTemplates.insert( fracTemplates.end(), stimPlanTemplates.begin(), stimPlanTemplates.end() ); QString tableText = createFractureText( fracTemplates ); textStream << tableText; textStream << lineStart << "\n"; } { std::vector wellPathFractures; for ( const auto& w : wellPaths ) { for ( const auto& frac : w->fractureCollection()->activeFractures() ) { wellPathFractures.push_back( frac ); } } std::sort( wellPathFractures.begin(), wellPathFractures.end(), RimWellPathFracture::compareByWellPathNameAndMD ); { QString tableText = createFractureInstancesText( wellPathFractures ); textStream << tableText; textStream << lineStart << "\n"; } { QString tableText = createFractureCompletionSummaryText( wellPathFractureReportItems ); textStream << tableText; textStream << lineStart << "\n"; } { QString tableText = createFracturePressureDepletionSummaryText( wellPathFractureReportItems ); textStream << tableText; textStream << lineStart << "\n"; } { textStream << lineStart << " Maximum number of connections per well\n"; textStream << lineStart << "\n"; QString tableText = createConnectionsPerWellText( wellPathFractureReportItems ); textStream << tableText; textStream << lineStart << "\n"; } } return text; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RicWellPathFractureTextReportFeatureImpl::wellPathsWithActiveFractures() { std::vector wellPaths; auto* wellPathColl = RimTools::wellPathCollection(); if ( wellPathColl ) { for ( const auto& wellPath : wellPathColl->allWellPaths() ) { if ( !wellPath->fractureCollection()->activeFractures().empty() ) { wellPaths.push_back( wellPath ); } } } return wellPaths; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createWellFileLocationText( const std::vector& wellPaths ) const { if ( wellPaths.empty() ) return ""; QString tableText; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "Well" ), RifTextDataTableColumn( "Location" ), }; formatter.header( header ); formatter.addHorizontalLine( '-' ); if ( !wellPaths.empty() ) { for ( const auto& wellPath : wellPaths ) { auto fileWellPath = dynamic_cast( wellPath ); if ( fileWellPath ) { formatter.add( wellPath->completionSettings()->wellNameForExport() ); formatter.add( fileWellPath->filePath() ); formatter.rowCompleted(); } } } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createStimPlanFileLocationText( const std::vector& stimPlanTemplates ) const { if ( stimPlanTemplates.empty() ) return ""; QString tableText; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "StimPlan Name" ), RifTextDataTableColumn( "Location" ), }; formatter.header( header ); formatter.addHorizontalLine( '-' ); if ( !stimPlanTemplates.empty() ) { for ( const auto& stimPlanTemplate : stimPlanTemplates ) { formatter.add( stimPlanTemplate->name() ); formatter.add( stimPlanTemplate->fileName() ); formatter.rowCompleted(); } } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createStimPlanFractureText( const std::vector& stimPlanTemplates ) const { if ( stimPlanTemplates.empty() ) return ""; QString tableText; RiaDefines::EclipseUnitSystem unitSystem = stimPlanTemplates.front()->fractureTemplateUnit(); bool isFieldUnits = unitSystem == RiaDefines::EclipseUnitSystem::UNITS_FIELD; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "StimPlan" ), RifTextDataTableColumn( " " ), floatNumberColumn( "WDiam" ), floatNumberColumn( "Skin" ), }; formatter.header( header ); // Second header line { formatter.add( "Template" ); // Template formatter.add( "Orientation" ); // Orientation formatter.add( isFieldUnits ? "[in]" : "[m]" ); // WDiam formatter.add( "[] " ); // Skin formatter.rowCompleted(); } formatter.addHorizontalLine( '-' ); for ( const auto& stimPlanTemplate : stimPlanTemplates ) { formatter.add( stimPlanTemplate->name() ); formatter.add( orientationText( stimPlanTemplate->orientationType() ) ); formatter.add( stimPlanTemplate->wellDiameter() ); formatter.add( stimPlanTemplate->skinFactor() ); formatter.rowCompleted(); } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createEllipseFractureText( const std::vector& ellipseTemplates ) const { if ( ellipseTemplates.empty() ) return ""; QString tableText; RiaDefines::EclipseUnitSystem unitSystem = ellipseTemplates.front()->fractureTemplateUnit(); bool isFieldUnits = unitSystem == RiaDefines::EclipseUnitSystem::UNITS_FIELD; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "Ellipse" ), RifTextDataTableColumn( " " ), floatNumberColumn( "Xf" ), floatNumberColumn( "Height" ), floatNumberColumn( "Kf" ), floatNumberColumn( "Wf" ), floatNumberColumn( "WDiam" ), floatNumberColumn( "Skin" ), }; formatter.header( header ); // Second header line { formatter.add( "Template" ); // Template formatter.add( "Orientation" ); // Orientation formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // Xf formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // Height formatter.add( "[mD]" ); // Kf formatter.add( isFieldUnits ? "[in]" : "[m]" ); // Wf formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // WDiam formatter.add( "[] " ); // Skin formatter.rowCompleted(); } formatter.addHorizontalLine( '-' ); for ( const auto& ellipseTemplate : ellipseTemplates ) { formatter.add( ellipseTemplate->name() ); formatter.add( orientationText( ellipseTemplate->orientationType() ) ); formatter.add( ellipseTemplate->halfLength() ); formatter.add( ellipseTemplate->height() ); formatter.add( RigTransmissibilityEquations::permeability( ellipseTemplate->conductivity(), ellipseTemplate->width() ) ); formatter.add( ellipseTemplate->width() ); formatter.add( ellipseTemplate->wellDiameter() ); formatter.add( ellipseTemplate->skinFactor() ); formatter.rowCompleted(); } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createFractureText( const std::vector& fractureTemplates ) const { if ( fractureTemplates.empty() ) return ""; QString tableText; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( " " ), floatNumberColumn( "Top" ), floatNumberColumn( "Bot" ), floatNumberColumn( "Fault" ), floatNumberColumn( "Height" ), floatNumberColumn( "Half Length" ), floatNumberColumn( "DFac" ), floatNumberColumn( "Conductivity" ), }; formatter.header( header ); // Second header line { formatter.add( "Template" ); formatter.add( "Cont" ); formatter.add( "Cont" ); formatter.add( "Truncation" ); formatter.add( "Scale" ); formatter.add( "Scale" ); formatter.add( "Scale" ); formatter.add( "Scale" ); formatter.rowCompleted(); } formatter.addHorizontalLine( '-' ); for ( const auto& fracTemplate : fractureTemplates ) { formatter.add( fracTemplate->name() ); if ( fracTemplate->fractureContainment()->isEnabled() ) { formatter.add( fracTemplate->fractureContainment()->topKLayer() ); formatter.add( fracTemplate->fractureContainment()->baseKLayer() ); } else { formatter.add( "N/A" ); formatter.add( "N/A" ); } if ( fracTemplate->fractureContainment()->minimumFaultThrow() >= 0.0 ) { formatter.add( fracTemplate->fractureContainment()->minimumFaultThrow() ); } else { formatter.add( "N/A" ); } double halfLengthScale, heightScale, dfactorScale, conductivityScale; fracTemplate->scaleFactors( &halfLengthScale, &heightScale, &dfactorScale, &conductivityScale ); formatter.add( heightScale ); formatter.add( halfLengthScale ); formatter.add( dfactorScale ); formatter.add( conductivityScale ); formatter.rowCompleted(); } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createFractureInstancesText( const std::vector& fractures ) const { if ( fractures.empty() ) return ""; RiaDefines::EclipseUnitSystem unitSystem = fractures.front()->fractureUnit(); // Fix bool isFieldUnits = unitSystem == RiaDefines::EclipseUnitSystem::UNITS_FIELD; QString tableText; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "" ), RifTextDataTableColumn( "" ), RifTextDataTableColumn( "" ), floatNumberColumn( "MD" ), floatNumberColumn( "Dip" ), floatNumberColumn( "Tilt" ), floatNumberColumn( "LPerf" ), floatNumberColumn( "PerfEff" ), floatNumberColumn( "Wdia" ), RifTextDataTableColumn( "Dfac", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ), RIGHT ), }; formatter.header( header ); // Second header line { formatter.add( "Well" ); formatter.add( "Fracture" ); formatter.add( "Template" ); formatter.add( "" ); // MD formatter.add( "" ); // Dip formatter.add( "" ); // Tilt formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // LPerf formatter.add( "[]" ); // PerfEff formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // WDia formatter.add( "[...]" ); // Dfac formatter.rowCompleted(); } formatter.addHorizontalLine( '-' ); for ( const auto& fracture : fractures ) { fracture->ensureValidNonDarcyProperties(); QString wellName; RimWellPath* wellPath = nullptr; fracture->firstAncestorOrThisOfType( wellPath ); if ( wellPath ) { wellName = wellPath->completionSettings()->wellNameForExport(); } formatter.add( wellName ); formatter.add( fracture->name() ); if ( fracture->fractureTemplate() ) { formatter.add( fracture->fractureTemplate()->name() ); } else { formatter.add( "N/A" ); } formatter.add( fracture->fractureMD() ); formatter.add( fracture->dip() ); formatter.add( fracture->tilt() ); if ( fracture->fractureTemplate() && fracture->fractureTemplate()->orientationType() == RimFractureTemplate::ALONG_WELL_PATH ) { formatter.add( fracture->perforationLength() ); } else { formatter.add( "N/A" ); } formatter.add( fracture->perforationEfficiency() ); formatter.add( fracture->wellRadius() * 2.0 ); if ( fracture->fractureTemplate() && fracture->fractureTemplate()->isNonDarcyFlowEnabled() ) { formatter.add( fracture->nonDarcyProperties().dFactor ); } else { formatter.add( "N/A" ); } formatter.rowCompleted(); } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createFractureCompletionSummaryText( const std::vector& wellPathFractureReportItems ) const { QString tableText; RiaDefines::EclipseUnitSystem unitSystem = wellPathFractureReportItems.front().unitSystem(); bool isFieldUnits = unitSystem == RiaDefines::EclipseUnitSystem::UNITS_FIELD; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); const QString meanText = "Mean"; std::vector header = { RifTextDataTableColumn( "" ), // Well RifTextDataTableColumn( "" ), // Fracture RifTextDataTableColumn( "" ), // Template floatNumberColumn( "" ), // Tr floatNumberColumn( "" ), //#con floatNumberColumn( "" ), // Fcd RifTextDataTableColumn( "", RifTextDataTableDoubleFormatting( RIF_FLOAT, 1 ), RIGHT ), // Area RifTextDataTableColumn( meanText, RifTextDataTableDoubleFormatting( RIF_FLOAT, 1 ), RIGHT ), // KfWf RifTextDataTableColumn( meanText, RifTextDataTableDoubleFormatting( RIF_FLOAT, 1 ), RIGHT ), // Kf floatNumberColumn( meanText ), // wf RifTextDataTableColumn( meanText, RifTextDataTableDoubleFormatting( RIF_FLOAT, 1 ), RIGHT ), // xf RifTextDataTableColumn( meanText, RifTextDataTableDoubleFormatting( RIF_FLOAT, 1 ), RIGHT ), // H floatNumberColumn( meanText ), // Km }; formatter.header( header ); // Second header line { formatter.add( "" ); formatter.add( "" ); formatter.add( "" ); formatter.add( "Tr" ); // Tr formatter.add( "#con" ); // #con formatter.add( "Fcd" ); // Fcd formatter.add( "Area" ); // Area formatter.add( "KfWf" ); // KfWf formatter.add( "Kf" ); // Kf formatter.add( "wf" ); // wf formatter.add( "Xf" ); // Xf formatter.add( "H" ); // H formatter.add( "Km" ); // Km formatter.rowCompleted(); } // Third header line { formatter.add( "Well" ); formatter.add( "Fracture" ); formatter.add( "Template" ); formatter.add( isFieldUnits ? "[cP.rb/day/psi]" : "[cP.rm3/day/bars]" ); // Tr formatter.add( "" ); // #con formatter.add( "[]" ); // Fcd formatter.add( isFieldUnits ? "[ft2]" : "[m2]" ); // Area formatter.add( isFieldUnits ? "[mDft]" : "[mDm]" ); // KfWf formatter.add( "[mD]" ); // Kf formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // wf formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // Xf formatter.add( isFieldUnits ? "[ft]" : "[m]" ); // H formatter.add( "[mD]" ); // Km formatter.rowCompleted(); } formatter.addHorizontalLine( '-' ); for ( const auto& reportItem : wellPathFractureReportItems ) { formatter.add( reportItem.wellPathNameForExport() ); formatter.add( reportItem.fractureName() ); formatter.add( reportItem.fractureTemplateName() ); formatter.add( reportItem.transmissibility() ); formatter.add( reportItem.connectionCount() ); formatter.add( reportItem.fcd() ); formatter.add( reportItem.area() ); formatter.add( reportItem.kfwf() ); // KfWf formatter.add( reportItem.kf() ); // Kf formatter.add( reportItem.wf() ); // wf formatter.add( reportItem.xf() ); // Xf formatter.add( reportItem.h() ); // H formatter.add( reportItem.km() ); // Km formatter.rowCompleted(); } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createFracturePressureDepletionSummaryText( const std::vector& wellPathFractureReportItems ) const { QString tableText; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "Well" ), RifTextDataTableColumn( "Fracture" ), RifTextDataTableColumn( "Actual WBHP" ), RifTextDataTableColumn( "Min Pressure Drop" ), RifTextDataTableColumn( "Max Pressure Drop" ) }; bool createdTable = false; for ( const auto& reportItem : wellPathFractureReportItems ) { if ( reportItem.performPressureDepletionScaling() ) { if ( !createdTable ) { formatter.comment( QString( "Pressure Depletion Time step: %1" ).arg( reportItem.pressureDepletionTimeStepString() ) ); formatter.comment( QString( "WBHP Source: %1" ).arg( reportItem.pressureDepletionWBHPString() ) ); formatter.comment( QString( "User Defined WBHP: %1" ).arg( reportItem.pressureDepletionUserWBHP() ) ); formatter.header( header ); formatter.addHorizontalLine( '-' ); createdTable = true; } formatter.add( reportItem.wellPathNameForExport() ); formatter.add( reportItem.fractureName() ); formatter.add( reportItem.pressureDepletionActualWBHP() ); formatter.add( reportItem.pressureDepletionMinPressureDrop() ); formatter.add( reportItem.pressureDepletionMaxPressureDrop() ); formatter.rowCompleted(); } } if ( createdTable ) { formatter.tableCompleted(); } return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicWellPathFractureTextReportFeatureImpl::createConnectionsPerWellText( const std::vector& wellPathFractureReportItems ) const { QString tableText; QTextStream stream( &tableText ); RifTextDataTableFormatter formatter( stream ); configureFormatter( &formatter ); std::vector header = { RifTextDataTableColumn( "Well" ), floatNumberColumn( "ConnCount" ) }; formatter.header( header ); formatter.addHorizontalLine( '-' ); std::map wellConnectionCounts; for ( const auto& reportItem : wellPathFractureReportItems ) { QString wellPathName = reportItem.wellPathNameForExport(); if ( wellConnectionCounts.find( wellPathName ) == wellConnectionCounts.end() ) { wellConnectionCounts.insert( std::make_pair( wellPathName, 0 ) ); } wellConnectionCounts[wellPathName] += reportItem.connectionCount(); } for ( const auto& connCount : wellConnectionCounts ) { formatter.add( connCount.first ); formatter.add( connCount.second ); formatter.rowCompleted(); } formatter.tableCompleted(); return tableText; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicWellPathFractureTextReportFeatureImpl::configureFormatter( RifTextDataTableFormatter* formatter ) const { if ( !formatter ) return; formatter->setColumnSpacing( 3 ); formatter->setTableRowPrependText( "-- " ); formatter->setTableRowLineAppendText( "" ); }