diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake index f9103b5ac5..6828067da2 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake @@ -2,6 +2,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicExportCompletionDataSettingsUi.cpp ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportCompletionDataFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportCompletionDataFeatureImpl.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicCompletionDataExportTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportMswCompletionsImpl.cpp ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportCompletionsFileTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RicFishbonesTransmissibilityCalculationFeatureImp.cpp diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicCompletionDataExportTools.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicCompletionDataExportTools.cpp new file mode 100644 index 0000000000..17f4988e68 --- /dev/null +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicCompletionDataExportTools.cpp @@ -0,0 +1,709 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2017 Statoil 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 "RicCompletionDataExportTools.h" + +#include "RiaLogging.h" +#include "RiaQStringFormatter.h" +#include "RiaWeightedMeanCalculator.h" +#include "RigStatisticsTools.h" + +#include "RicPerforationCellFilterEvaluator.h" +#include "RicTransmissibilityCalculator.h" +#include "RicWellPathExportCompletionDataFeatureImpl.h" + +#include "RifTextDataTableFormatter.h" + +#include "RigActiveCellInfo.h" +#include "RigEclipseCaseData.h" +#include "RigMainGrid.h" +#include "Well/RigWellLogExtractor.h" +#include "Well/RigWellPath.h" +#include "Well/RigWellPathIntersectionTools.h" + +#include "RimCellFilter.h" +#include "RimEclipseCase.h" +#include "RimNonDarcyPerforationParameters.h" +#include "RimPerforationCollection.h" +#include "RimPerforationInterval.h" +#include "RimWellPath.h" +#include "RimWellPathCompletions.h" + +#include "cafAssert.h" + +#include +#include +#include +#include +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigCompletionData RicCompletionDataExportTools::combineEclipseCellCompletions( const std::vector& completions, + const RicExportCompletionDataSettingsUi& settings ) +{ + CAF_ASSERT( !completions.empty() ); + + // For detailed description of how the combined completion data is computed, see + // https://github.com/OPM/ResInsight/issues/7049 + + const RigCompletionData& firstCompletion = completions[0]; + + const QString& wellName = firstCompletion.wellName(); + const RigCompletionDataGridCell& cellIndexIJK = firstCompletion.completionDataGridCell(); + RigCompletionData::CompletionType completionType = firstCompletion.completionType(); + + RigCompletionData resultCompletion( wellName, cellIndexIJK, firstCompletion.firstOrderingValue() ); + resultCompletion.setSecondOrderingValue( firstCompletion.secondOrderingValue() ); + resultCompletion.setSourcePdmObject( firstCompletion.sourcePdmObject() ); + + RigCompletionData::CellDirection cellDirection = firstCompletion.direction(); + double largestTransmissibilityValue = firstCompletion.transmissibility(); + + RiaWeightedMeanCalculator diameterCalculator; + RiaWeightedMeanCalculator skinFactorCalculator; + + auto isValidTransmissibility = []( double transmissibility ) + { return RigStatisticsTools::isValidNumber( transmissibility ) && transmissibility >= 0.0; }; + + auto startMD = completions[0].startMD(); + auto endMD = completions[0].endMD(); + + for ( const RigCompletionData& completion : completions ) + { + if ( !startMD.has_value() ) + { + startMD = completion.startMD(); + } + else + { + if ( completion.startMD().has_value() && ( completion.startMD().value() < startMD.value() ) ) + { + startMD = completion.startMD(); + } + } + + if ( !endMD.has_value() ) + { + endMD = completion.endMD(); + } + else + { + if ( completion.endMD().has_value() && ( completion.endMD().value() > endMD.value() ) ) + { + endMD = completion.endMD(); + } + } + + double transmissibility = completion.transmissibility(); + + if ( !isValidTransmissibility( transmissibility ) ) + { + QString errorMessage = QString( "Invalid or negative transmissibility value (%1) in cell %2" ) + .arg( transmissibility ) + .arg( cellIndexIJK.oneBasedLocalCellIndexString() ); + RiaLogging::error( errorMessage.toStdString() ); + resultCompletion.addMetadata( "ERROR", errorMessage ); + continue; + } + + diameterCalculator.addValueAndWeight( completion.diameter(), transmissibility ); + skinFactorCalculator.addValueAndWeight( completion.skinFactor(), transmissibility ); + + if ( transmissibility > largestTransmissibilityValue ) + { + largestTransmissibilityValue = transmissibility; + cellDirection = completion.direction(); + } + } + + if ( startMD.has_value() && endMD.has_value() ) + { + resultCompletion.setDepthRange( startMD.value(), endMD.value() ); + } + if ( auto completionNumber = completionNumberForCell( completions ) ) resultCompletion.setCompletionNumber( completionNumber.value() ); + + double combinedDiameter = diameterCalculator.weightedMean(); + double combinedSkinFactor = skinFactorCalculator.weightedMean(); + + double combinedTrans = 0.0; + double combinedKh = 0.0; + double combinedDFactor = 0.0; + + { + RiaWeightedMeanCalculator dFactorCalculator; + size_t validCompletionCount = 0; + for ( const RigCompletionData& completion : completions ) + { + // Error is reported in the loop above, so we can skip this completion + if ( !isValidTransmissibility( completion.transmissibility() ) ) continue; + + resultCompletion.m_metadata.reserve( resultCompletion.m_metadata.size() + completion.m_metadata.size() ); + resultCompletion.m_metadata.insert( resultCompletion.m_metadata.end(), completion.m_metadata.begin(), completion.m_metadata.end() ); + + if ( completion.wellName() != firstCompletion.wellName() ) + { + QString errorMessage = + QString( "Cannot combine completions of different types in same cell %1" ).arg( cellIndexIJK.oneBasedLocalCellIndexString() ); + RiaLogging::error( errorMessage.toStdString() ); + resultCompletion.addMetadata( "ERROR", errorMessage ); + return resultCompletion; // Returning empty completion, should not be exported + } + + combinedTrans = combinedTrans + completion.transmissibility(); + combinedKh = combinedKh + completion.kh(); + validCompletionCount++; + + dFactorCalculator.addValueAndWeight( completion.dFactor(), completion.transmissibility() ); + } + + // Arithmetic MEAN dFactor weighted by Tj/SumTj from the completions + // Note : Divide by n is intentional, based on input from @hhgs in mail dated 18.01.2020 + if ( validCompletionCount > 0 ) combinedDFactor = dFactorCalculator.weightedMean() / validCompletionCount; + } + + if ( settings.compdatExport == RicExportCompletionDataSettingsUi::CompdatExport::TRANSMISSIBILITIES ) + { + resultCompletion.setCombinedValuesExplicitTrans( combinedTrans, + combinedKh, + combinedDFactor, + combinedSkinFactor, + combinedDiameter, + cellDirection, + completionType ); + } + else if ( settings.compdatExport == RicExportCompletionDataSettingsUi::CompdatExport::WPIMULT_AND_DEFAULT_CONNECTION_FACTORS ) + { + // calculate trans for main bore - but as Eclipse will do it! + double transmissibilityEclipseCalculation = + RicTransmissibilityCalculator::calculateTransmissibilityAsEclipseDoes( settings.caseToApply(), + combinedSkinFactor, + combinedDiameter / 2, + cellIndexIJK.globalCellIndex(), + cellDirection ); + + double defaultKh = RigCompletionData::defaultValue(); + double wpimult = combinedTrans / transmissibilityEclipseCalculation; + resultCompletion.setCombinedValuesImplicitTransWPImult( wpimult, + defaultKh, + combinedDFactor, + combinedSkinFactor, + combinedDiameter, + cellDirection, + completionType ); + } + + return resultCompletion; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RicCompletionDataExportTools::mainGridCompletions( const std::vector& allCompletions ) +{ + std::vector completions; + + for ( const auto& completion : allCompletions ) + { + QString gridName = completion.completionDataGridCell().lgrName(); + if ( gridName.isEmpty() ) + { + completions.push_back( completion ); + } + } + return completions; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::map> + RicCompletionDataExportTools::subGridsCompletions( const std::vector& allCompletions ) +{ + std::map> completions; + + for ( const auto& completion : allCompletions ) + { + QString gridName = completion.completionDataGridCell().lgrName(); + if ( !gridName.isEmpty() ) + { + auto it = completions.find( gridName ); + if ( it == completions.end() ) + { + completions.insert( std::pair>( gridName, { completion } ) ); + } + else + { + it->second.push_back( completion ); + } + } + } + return completions; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCompletionDataExportTools::exportCompdatTableUsingFormatter( RifTextDataTableFormatter& formatter, + const QString& gridName, + const std::vector& completionData ) +{ + if ( gridName.isEmpty() ) + { + std::vector header = + { RifTextDataTableColumn( "WELL", "NAME" ), + RifTextDataTableColumn( "", "I" ), + RifTextDataTableColumn( "", "J" ), + RifTextDataTableColumn( "", "K1" ), + RifTextDataTableColumn( "", "K2" ), + RifTextDataTableColumn( "OPEN", "SHUT" ), + RifTextDataTableColumn( "SAT", "TAB" ), + RifTextDataTableColumn( "CONN", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), + RifTextDataTableColumn( "WELL", "DIA" ), + RifTextDataTableColumn( "KH", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), + RifTextDataTableColumn( "SKIN", "FACT" ), + RifTextDataTableColumn( "D", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), + RifTextDataTableColumn( "DIR", "PEN" ) }; + + formatter.header( header ); + formatter.keyword( "COMPDAT" ); + } + else + { + std::vector header = + { RifTextDataTableColumn( "WELL", "NAME" ), + RifTextDataTableColumn( "LGR", "NAME" ), + RifTextDataTableColumn( "", "I" ), + RifTextDataTableColumn( "", "J" ), + RifTextDataTableColumn( "", "K1" ), + RifTextDataTableColumn( "", "K2" ), + RifTextDataTableColumn( "OPEN", "SHUT" ), + RifTextDataTableColumn( "SAT", "TAB" ), + RifTextDataTableColumn( "CONN", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), + RifTextDataTableColumn( "WELL", "DIA" ), + RifTextDataTableColumn( "KH", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), + RifTextDataTableColumn( "SKIN", "FACT" ), + RifTextDataTableColumn( "D", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), + RifTextDataTableColumn( "DIR", "PEN" ) }; + + formatter.header( header ); + formatter.keyword( "COMPDATL" ); + } + + RigCompletionData::CompletionType currentCompletionType = RigCompletionData::CompletionType::CT_UNDEFINED; + + for ( const RigCompletionData& data : completionData ) + { + if ( currentCompletionType != data.completionType() ) + { + // The completions are sorted by completion type, write out a heading when completion type changes + + QString txt; + if ( data.completionType() == RigCompletionData::CompletionType::FISHBONES ) txt = "Fishbones"; + if ( data.completionType() == RigCompletionData::CompletionType::FRACTURE ) txt = "Fracture"; + if ( data.completionType() == RigCompletionData::CompletionType::PERFORATION ) txt = "Perforation"; + + formatter.addOptionalComment( "---- Completions for completion type " + txt + " ----" ); + + currentCompletionType = data.completionType(); + } + + for ( const RigCompletionMetaData& metadata : data.metadata() ) + { + formatter.addOptionalComment( QString( "%1 : %2" ).arg( metadata.name ).arg( metadata.comment ) ); + } + + if ( data.transmissibility() == 0.0 || data.wpimult() == 0.0 ) + { + // Don't export completions without transmissibility + continue; + } + + formatter.add( data.wellName() ); + + if ( !gridName.isEmpty() ) + { + formatter.add( data.completionDataGridCell().lgrName() ); + } + + formatter.add( data.completionDataGridCell().localCellIndexI() + 1 ) + .add( data.completionDataGridCell().localCellIndexJ() + 1 ) + .add( data.completionDataGridCell().localCellIndexK() + 1 ) + .add( data.completionDataGridCell().localCellIndexK() + 1 ); + + formatter.add( "OPEN" ); + + formatter.addValueOrDefaultMarker( data.saturation(), RigCompletionData::defaultValue() ); + formatter.addValueOrDefaultMarker( data.transmissibility(), RigCompletionData::defaultValue() ); + formatter.addValueOrDefaultMarker( data.diameter(), RigCompletionData::defaultValue() ); + formatter.addValueOrDefaultMarker( data.kh(), RigCompletionData::defaultValue() ); + formatter.addValueOrDefaultMarker( data.skinFactor(), RigCompletionData::defaultValue() ); + formatter.addValueOrDefaultMarker( data.dFactor(), RigCompletionData::defaultValue() ); + + switch ( data.direction() ) + { + case RigCompletionData::CellDirection::DIR_I: + formatter.add( "'X'" ); + break; + case RigCompletionData::CellDirection::DIR_J: + formatter.add( "'Y'" ); + break; + case RigCompletionData::CellDirection::DIR_K: + default: + formatter.add( "'Z'" ); + break; + } + + formatter.rowCompleted(); + } + formatter.tableCompleted(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCompletionDataExportTools::exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter, + const QString& gridName, + const std::vector& completionData ) +{ + if ( gridName.isEmpty() ) + { + std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), + RifTextDataTableColumn( "", "I" ), + RifTextDataTableColumn( "", "J" ), + RifTextDataTableColumn( "", "K1" ), + RifTextDataTableColumn( "", "K2" ), + RifTextDataTableColumn( "COMP", "NUM" ) }; + + formatter.header( header ); + formatter.keyword( "COMPLUMP" ); + } + else + { + std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), + RifTextDataTableColumn( "LGR", "NAME" ), + RifTextDataTableColumn( "", "I" ), + RifTextDataTableColumn( "", "J" ), + RifTextDataTableColumn( "", "K1" ), + RifTextDataTableColumn( "", "K2" ), + RifTextDataTableColumn( "COMP", "NUM" ) }; + + formatter.header( header ); + formatter.keyword( "COMPLMPL" ); + } + + for ( const RigCompletionData& data : completionData ) + { + if ( !data.completionNumber().has_value() || data.transmissibility() == 0.0 || data.wpimult() == 0.0 ) + { + // Don't export completions without transmissibility or completion number set + continue; + } + + formatter.add( data.wellName() ); + + if ( !gridName.isEmpty() ) + { + formatter.add( data.completionDataGridCell().lgrName() ); + } + + formatter.add( data.completionDataGridCell().localCellIndexI() + 1 ) + .add( data.completionDataGridCell().localCellIndexJ() + 1 ) + .add( data.completionDataGridCell().localCellIndexK() + 1 ) + .add( data.completionDataGridCell().localCellIndexK() + 1 ) + .add( data.completionNumber().value() ); + + formatter.rowCompleted(); + } + formatter.tableCompleted(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCompletionDataExportTools::exportWpimultTableUsingFormatter( RifTextDataTableFormatter& formatter, + const QString& gridName, + const std::vector& completionData ) +{ + std::vector header; + + if ( gridName.isEmpty() ) + { + header = { + RifTextDataTableColumn( "Well" ), + RifTextDataTableColumn( "Mult" ), + RifTextDataTableColumn( "I" ), + RifTextDataTableColumn( "J" ), + RifTextDataTableColumn( "K" ), + }; + formatter.keyword( "WPIMULT" ); + } + else + { + header = { + RifTextDataTableColumn( "Well" ), + RifTextDataTableColumn( "LgrName" ), + RifTextDataTableColumn( "Mult" ), + RifTextDataTableColumn( "I" ), + RifTextDataTableColumn( "J" ), + RifTextDataTableColumn( "K" ), + }; + formatter.keyword( "WPIMULTL" ); + } + formatter.header( header ); + + for ( auto& completion : completionData ) + { + if ( completion.wpimult() == 0.0 || RigCompletionData::isDefaultValue( completion.wpimult() ) ) + { + continue; + } + + formatter.add( completion.wellName() ); + + if ( !gridName.isEmpty() ) + { + formatter.add( completion.completionDataGridCell().lgrName() ); + } + + formatter.add( completion.wpimult() ); + + formatter.add( completion.completionDataGridCell().localCellIndexI() + 1 ) + .add( completion.completionDataGridCell().localCellIndexJ() + 1 ) + .add( completion.completionDataGridCell().localCellIndexK() + 1 ); + formatter.rowCompleted(); + } + + formatter.tableCompleted(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector + RicCompletionDataExportTools::generatePerforationsCompdatValues( const RimWellPath* wellPath, + const std::vector& intervals, + const RicExportCompletionDataSettingsUi& settings, + const std::optional& exportDate ) +{ + RiaDefines::EclipseUnitSystem unitSystem = settings.caseToApply->eclipseCaseData()->unitsType(); + + std::vector completionData; + + const RigActiveCellInfo* activeCellInfo = + settings.caseToApply->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + auto wellPathGeometry = wellPath->wellPathGeometry(); + if ( !wellPathGeometry ) return completionData; + + const RimNonDarcyPerforationParameters* nonDarcyParameters = wellPath->perforationIntervalCollection()->nonDarcyParameters(); + + if ( wellPath->perforationIntervalCollection()->isChecked() ) + { + for ( const RimPerforationInterval* interval : intervals ) + { + if ( !interval->isChecked() ) continue; + if ( exportDate.has_value() && !interval->isActiveOnDate( *exportDate ) ) continue; + + std::pair, std::vector> perforationPointsAndMD = + wellPathGeometry->clippedPointSubset( interval->startMD(), interval->endMD() ); + + std::vector intersectedCells = + RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( settings.caseToApply->eclipseCaseData(), + wellPath->name(), + perforationPointsAndMD.first, + perforationPointsAndMD.second ); + + RicPerforationCellFilterEvaluator filterEval( interval->cellFilter(), settings.caseToApply ); + + const size_t completionCountBefore = completionData.size(); + + for ( auto& cell : intersectedCells ) + { + bool cellIsActive = activeCellInfo->isActive( ReservoirCellIndex( cell.globCellIndex ) ); + if ( !cellIsActive ) continue; + if ( !filterEval.includesGlobalCell( cell.globCellIndex ) ) continue; + + RigCompletionData completion( wellPath->completionSettings()->wellNameForExport(), + RigCompletionDataGridCell( cell.globCellIndex, settings.caseToApply->mainGrid() ), + cell.startMD ); + + RigCompletionData::CellDirection direction = + RicTransmissibilityCalculator::calculateCellMainDirection( settings.caseToApply, + cell.globCellIndex, + cell.intersectionLengthsInCellCS ); + + double transmissibility = 0.0; + double kh = RigCompletionData::defaultValue(); + double dFactor = RigCompletionData::defaultValue(); + + { + auto transmissibilityData = + RicTransmissibilityCalculator::calculateTransmissibilityData( settings.caseToApply, + wellPath, + cell.intersectionLengthsInCellCS, + interval->skinFactor(), + interval->diameter( unitSystem ) / 2, + cell.globCellIndex, + settings.useLateralNTG ); + + transmissibility = transmissibilityData.connectionFactor(); + kh = transmissibilityData.kh(); + + if ( nonDarcyParameters->nonDarcyFlowType() == RimNonDarcyPerforationParameters::NON_DARCY_COMPUTED ) + { + const double effectiveH = transmissibilityData.effectiveH(); + + const double effectivePermeability = + nonDarcyParameters->gridPermeabilityScalingFactor() * transmissibilityData.effectiveK(); + + dFactor = + RicTransmissibilityCalculator::calculateDFactor( settings.caseToApply, + effectiveH, + cell.globCellIndex, + wellPath->perforationIntervalCollection()->nonDarcyParameters(), + effectivePermeability ); + } + } + + completion.setTransAndWPImultBackgroundDataFromPerforation( transmissibility, + interval->skinFactor(), + interval->diameter( unitSystem ), + dFactor, + kh, + direction ); + completion.setDepthRange( cell.startMD, cell.endMD ); + if ( interval->completionNumber() > 0 ) completion.setCompletionNumber( interval->completionNumber() ); + + completion.addMetadata( "Perforation Completion", + QString( "MD In: %1 - MD Out: %2" ).arg( cell.startMD ).arg( cell.endMD ) + + QString( " Transmissibility: " ) + QString::number( transmissibility ) ); + completion.setSourcePdmObject( interval ); + completionData.push_back( completion ); + } + + if ( filterEval.isEnabled() && completionData.size() == completionCountBefore && filterEval.rejectedCellCount() > 0 ) + { + RiaLogging::info( + std::format( "Perforation '{}' on well '{}': all {} intersected cells suppressed by attached cell filter '{}'.", + interval->name(), + wellPath->name(), + filterEval.rejectedCellCount(), + interval->cellFilter() ? interval->cellFilter()->fullName() : QString() ) ); + } + } + } + + if ( nonDarcyParameters->nonDarcyFlowType() == RimNonDarcyPerforationParameters::NON_DARCY_USER_DEFINED ) + { + // Compute D-factor for completion as + // D_cell = D_well * Sum_Tran_cells / Tran_cell + // + // See https://github.com/OPM/ResInsight/issues/7450 + + double accumulatedTransmissibility = 0.0; + for ( const auto& completion : completionData ) + { + accumulatedTransmissibility += completion.transmissibility(); + } + + double userDefinedDFactor = nonDarcyParameters->userDefinedDFactor(); + for ( auto& completion : completionData ) + { + // A completion without transmissibility has no meaningful D-factor + if ( completion.transmissibility() <= 0.0 ) continue; + + double dFactorForCompletion = userDefinedDFactor * accumulatedTransmissibility / completion.transmissibility(); + completion.setDFactor( dFactorForCompletion ); + } + } + + return completionData; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCompletionDataExportTools::appendCompletionData( std::map>* completionData, + const std::vector& completionsToAppend ) +{ + for ( const auto& completion : completionsToAppend ) + { + auto it = completionData->find( completion.completionDataGridCell().globalCellIndex() ); + if ( it != completionData->end() ) + { + it->second.push_back( completion ); + } + else + { + completionData->insert( std::pair>( completion.completionDataGridCell().globalCellIndex(), + std::vector{ completion } ) ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellPath* RicCompletionDataExportTools::topLevelWellPath( const RigCompletionData& completion ) +{ + if ( completion.sourcePdmObject() ) + { + auto parentWellPath = completion.sourcePdmObject()->firstAncestorOrThisOfType(); + if ( parentWellPath ) + { + return parentWellPath->topLevelWellPath(); + } + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RicCompletionDataExportTools::completionNumberForCell( const std::vector& completions ) +{ + // One perforation interval can cross the same cell more than once, so lengths are summed per number + std::map overlapPerNumber; + for ( const auto& completion : completions ) + { + const auto number = completion.completionNumber(); + if ( !number.has_value() ) continue; + + double length = 0.0; + if ( completion.startMD().has_value() && completion.endMD().has_value() ) + { + length = std::max( 0.0, completion.endMD().value() - completion.startMD().value() ); + } + overlapPerNumber[number.value()] += length; + } + + if ( overlapPerNumber.empty() ) return std::nullopt; + + // Iterating in ascending number order and requiring a strictly larger length makes ties go to the lowest number + const double tolerance = 1.0e-6; + + auto best = overlapPerNumber.begin(); + for ( auto it = std::next( best ); it != overlapPerNumber.end(); ++it ) + { + if ( it->second > best->second + tolerance ) best = it; + } + return best->first; +} diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicCompletionDataExportTools.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicCompletionDataExportTools.h new file mode 100644 index 0000000000..24e20d4809 --- /dev/null +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicCompletionDataExportTools.h @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2017 Statoil 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. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RigCompletionData.h" + +#include "RicExportCompletionDataSettingsUi.h" + +#include +#include + +#include +#include +#include + +class RifTextDataTableFormatter; +class RimPerforationInterval; +class RimWellPath; + +//================================================================================================== +/// Building blocks used when generating and writing COMPDAT, COMPLUMP and WPIMULT data +//================================================================================================== +class RicCompletionDataExportTools +{ +public: + static std::vector generatePerforationsCompdatValues( const RimWellPath* wellPath, + const std::vector& intervals, + const RicExportCompletionDataSettingsUi& settings, + const std::optional& exportDate = std::nullopt ); + + static RigCompletionData combineEclipseCellCompletions( const std::vector& completions, + const RicExportCompletionDataSettingsUi& settings ); + + static std::vector mainGridCompletions( const std::vector& allCompletions ); + + static std::map> subGridsCompletions( const std::vector& allCompletions ); + + static void exportCompdatTableUsingFormatter( RifTextDataTableFormatter& formatter, + const QString& gridName, + const std::vector& completionData ); + + static void exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter, + const QString& gridName, + const std::vector& completionData ); + + static void exportWpimultTableUsingFormatter( RifTextDataTableFormatter& formatter, + const QString& gridName, + const std::vector& completionData ); + + static void appendCompletionData( std::map>* completionData, + const std::vector& data ); + + static RimWellPath* topLevelWellPath( const RigCompletionData& completion ); + + // Completion number for a cell with several contributions: the number with the largest summed MD overlap + // (endMD - startMD) wins, ties go to the lowest number. Independent of contribution order. + static std::optional completionNumberForCell( const std::vector& completions ); +}; diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp index 346d05e782..20a0850dcf 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp @@ -21,11 +21,10 @@ #include "RiaLogging.h" #include "RiaPreferencesSystem.h" #include "RiaQStringFormatter.h" -#include "RiaWeightedMeanCalculator.h" -#include "RigStatisticsTools.h" #include "RiuMessageDialog.h" #include "ExportCommands/RicExportLgrFeature.h" +#include "RicCompletionDataExportTools.h" #include "RicExportCompletionDataSettingsUi.h" #include "RicExportFractureCompletionsImpl.h" #include "RicFishbonesTransmissibilityCalculationFeatureImp.h" @@ -46,14 +45,10 @@ #include "Well/RigWellPath.h" #include "Well/RigWellPathIntersectionTools.h" -#include "RicPerforationCellFilterEvaluator.h" - #include "RimCellFilter.h" #include "RimEclipseCase.h" #include "RimFishbonesCollection.h" -#include "RimNonDarcyPerforationParameters.h" #include "RimPerforationCollection.h" -#include "RimPerforationInterval.h" #include "RimThermalFractureTemplate.h" #include "RimWellPath.h" #include "RimWellPathCompletions.h" @@ -201,12 +196,15 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompletions( const std::v { auto exportDate = exportDateForTimeStep( exportSettings.caseToApply, exportSettings.timeStep ); std::vector perforationCompletionData = - generatePerforationsCompdatValues( wellPathLateral, - wellPathLateral->perforationIntervalCollection()->perforations(), - exportSettings, - exportDate ); + RicCompletionDataExportTools::generatePerforationsCompdatValues( wellPathLateral, + wellPathLateral + ->perforationIntervalCollection() + ->perforations(), + exportSettings, + exportDate ); - appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, perforationCompletionData ); + RicCompletionDataExportTools::appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, + perforationCompletionData ); } if ( exportSettings.includeFishbones ) @@ -218,7 +216,8 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompletions( const std::v RicFishbonesTransmissibilityCalculationFeatureImp::generateFishboneCompdatValuesUsingAdjustedCellVolume( wellPathLateral, exportSettings ); - appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fishbonesCompletionData ); + RicCompletionDataExportTools::appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, + fishbonesCompletionData ); } if ( exportSettings.includeFractures() ) @@ -237,13 +236,14 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompletions( const std::v exportSettings.transScalingWBHPSource(), exportSettings.transScalingWBHP() ) ); - appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fractureCompletionData ); + RicCompletionDataExportTools::appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, + fractureCompletionData ); } } for ( auto& data : completionsPerEclipseCellAllCompletionTypes ) { - completions.push_back( combineEclipseCellCompletions( data.second, exportSettings ) ); + completions.push_back( RicCompletionDataExportTools::combineEclipseCellCompletions( data.second, exportSettings ) ); } progress.incrementProgress(); @@ -282,7 +282,7 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompletions( const std::v std::vector completionsForWell; for ( const auto& completion : completions ) { - if ( wellPath == topLevelWellPath( completion ) ) + if ( wellPath == RicCompletionDataExportTools::topLevelWellPath( completion ) ) { completionsForWell.push_back( completion ); } @@ -376,259 +376,15 @@ std::vector RicWellPathExportCompletionDataFeatureImpl::compu auto exportDate = exportDateForTimeStep( eclipseCase, timeStepIndex ); completionsPerEclipseCell = - generatePerforationsCompdatValues( wellPath, wellPath->perforationIntervalCollection()->perforations(), exportSettings, exportDate ); + RicCompletionDataExportTools::generatePerforationsCompdatValues( wellPath, + wellPath->perforationIntervalCollection()->perforations(), + exportSettings, + exportDate ); } return completionsPerEclipseCell; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -std::optional RicWellPathExportCompletionDataFeatureImpl::completionNumberForCell( const std::vector& completions ) -{ - // One perforation interval can cross the same cell more than once, so lengths are summed per number - std::map overlapPerNumber; - for ( const auto& completion : completions ) - { - const auto number = completion.completionNumber(); - if ( !number.has_value() ) continue; - - double length = 0.0; - if ( completion.startMD().has_value() && completion.endMD().has_value() ) - { - length = std::max( 0.0, completion.endMD().value() - completion.startMD().value() ); - } - overlapPerNumber[number.value()] += length; - } - - if ( overlapPerNumber.empty() ) return std::nullopt; - - // Iterating in ascending number order and requiring a strictly larger length makes ties go to the lowest number - const double tolerance = 1.0e-6; - - auto best = overlapPerNumber.begin(); - for ( auto it = std::next( best ); it != overlapPerNumber.end(); ++it ) - { - if ( it->second > best->second + tolerance ) best = it; - } - return best->first; -} - -//================================================================================================== -/// -//================================================================================================== -RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCellCompletions( const std::vector& completions, - const RicExportCompletionDataSettingsUi& settings ) -{ - CAF_ASSERT( !completions.empty() ); - - // For detailed description of how the combined completion data is computed, see - // https://github.com/OPM/ResInsight/issues/7049 - - const RigCompletionData& firstCompletion = completions[0]; - - const QString& wellName = firstCompletion.wellName(); - const RigCompletionDataGridCell& cellIndexIJK = firstCompletion.completionDataGridCell(); - RigCompletionData::CompletionType completionType = firstCompletion.completionType(); - - RigCompletionData resultCompletion( wellName, cellIndexIJK, firstCompletion.firstOrderingValue() ); - resultCompletion.setSecondOrderingValue( firstCompletion.secondOrderingValue() ); - resultCompletion.setSourcePdmObject( firstCompletion.sourcePdmObject() ); - - RigCompletionData::CellDirection cellDirection = firstCompletion.direction(); - double largestTransmissibilityValue = firstCompletion.transmissibility(); - - RiaWeightedMeanCalculator diameterCalculator; - RiaWeightedMeanCalculator skinFactorCalculator; - - auto isValidTransmissibility = []( double transmissibility ) - { return RigStatisticsTools::isValidNumber( transmissibility ) && transmissibility >= 0.0; }; - - auto startMD = completions[0].startMD(); - auto endMD = completions[0].endMD(); - - for ( const RigCompletionData& completion : completions ) - { - if ( !startMD.has_value() ) - { - startMD = completion.startMD(); - } - else - { - if ( completion.startMD().has_value() && ( completion.startMD().value() < startMD.value() ) ) - { - startMD = completion.startMD(); - } - } - - if ( !endMD.has_value() ) - { - endMD = completion.endMD(); - } - else - { - if ( completion.endMD().has_value() && ( completion.endMD().value() > endMD.value() ) ) - { - endMD = completion.endMD(); - } - } - - double transmissibility = completion.transmissibility(); - - if ( !isValidTransmissibility( transmissibility ) ) - { - QString errorMessage = QString( "Invalid or negative transmissibility value (%1) in cell %3" ) - .arg( transmissibility ) - .arg( cellIndexIJK.oneBasedLocalCellIndexString() ); - RiaLogging::error( errorMessage.toStdString() ); - resultCompletion.addMetadata( "ERROR", errorMessage ); - continue; - } - - diameterCalculator.addValueAndWeight( completion.diameter(), transmissibility ); - skinFactorCalculator.addValueAndWeight( completion.skinFactor(), transmissibility ); - - if ( transmissibility > largestTransmissibilityValue ) - { - largestTransmissibilityValue = transmissibility; - cellDirection = completion.direction(); - } - } - - if ( startMD.has_value() && endMD.has_value() ) - { - resultCompletion.setDepthRange( startMD.value(), endMD.value() ); - } - if ( auto completionNumber = completionNumberForCell( completions ) ) resultCompletion.setCompletionNumber( completionNumber.value() ); - - double combinedDiameter = diameterCalculator.weightedMean(); - double combinedSkinFactor = skinFactorCalculator.weightedMean(); - - double combinedTrans = 0.0; - double combinedKh = 0.0; - double combinedDFactor = 0.0; - - { - RiaWeightedMeanCalculator dFactorCalculator; - - for ( const RigCompletionData& completion : completions ) - { - // Error is reported in the loop above, so we can skip this completion - if ( !isValidTransmissibility( completion.transmissibility() ) ) continue; - - resultCompletion.m_metadata.reserve( resultCompletion.m_metadata.size() + completion.m_metadata.size() ); - resultCompletion.m_metadata.insert( resultCompletion.m_metadata.end(), completion.m_metadata.begin(), completion.m_metadata.end() ); - - if ( completion.wellName() != firstCompletion.wellName() ) - { - QString errorMessage = - QString( "Cannot combine completions of different types in same cell %1" ).arg( cellIndexIJK.oneBasedLocalCellIndexString() ); - RiaLogging::error( errorMessage.toStdString() ); - resultCompletion.addMetadata( "ERROR", errorMessage ); - return resultCompletion; // Returning empty completion, should not be exported - } - - if ( completion.transmissibility() == HUGE_VAL ) - { - QString errorMessage = - QString( "Transmissibility calculation has failed for cell %1" ).arg( cellIndexIJK.oneBasedLocalCellIndexString() ); - RiaLogging::error( errorMessage.toStdString() ); - resultCompletion.addMetadata( "ERROR", errorMessage ); - return resultCompletion; // Returning empty completion, should not be exported - } - - combinedTrans = combinedTrans + completion.transmissibility(); - combinedKh = combinedKh + completion.kh(); - - dFactorCalculator.addValueAndWeight( completion.dFactor(), completion.transmissibility() ); - } - - // Arithmetic MEAN dFactor weighted by Tj/SumTj from the completions - // Note : Divide by n is intentional, based on input from @hhgs in mail dated 18.01.2020 - combinedDFactor = dFactorCalculator.weightedMean() / completions.size(); - } - - if ( settings.compdatExport == RicExportCompletionDataSettingsUi::CompdatExport::TRANSMISSIBILITIES ) - { - resultCompletion.setCombinedValuesExplicitTrans( combinedTrans, - combinedKh, - combinedDFactor, - combinedSkinFactor, - combinedDiameter, - cellDirection, - completionType ); - } - else if ( settings.compdatExport == RicExportCompletionDataSettingsUi::CompdatExport::WPIMULT_AND_DEFAULT_CONNECTION_FACTORS ) - { - // calculate trans for main bore - but as Eclipse will do it! - double transmissibilityEclipseCalculation = - RicTransmissibilityCalculator::calculateTransmissibilityAsEclipseDoes( settings.caseToApply(), - combinedSkinFactor, - combinedDiameter / 2, - cellIndexIJK.globalCellIndex(), - cellDirection ); - - double defaultKh = RigCompletionData::defaultValue(); - double wpimult = combinedTrans / transmissibilityEclipseCalculation; - resultCompletion.setCombinedValuesImplicitTransWPImult( wpimult, - defaultKh, - combinedDFactor, - combinedSkinFactor, - combinedDiameter, - cellDirection, - completionType ); - } - - return resultCompletion; -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -std::vector - RicWellPathExportCompletionDataFeatureImpl::mainGridCompletions( const std::vector& allCompletions ) -{ - std::vector completions; - - for ( const auto& completion : allCompletions ) - { - QString gridName = completion.completionDataGridCell().lgrName(); - if ( gridName.isEmpty() ) - { - completions.push_back( completion ); - } - } - return completions; -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -std::map> - RicWellPathExportCompletionDataFeatureImpl::subGridsCompletions( const std::vector& allCompletions ) -{ - std::map> completions; - - for ( const auto& completion : allCompletions ) - { - QString gridName = completion.completionDataGridCell().lgrName(); - if ( !gridName.isEmpty() ) - { - auto it = completions.find( gridName ); - if ( it == completions.end() ) - { - completions.insert( std::pair>( gridName, { completion } ) ); - } - else - { - it->second.push_back( completion ); - } - } - } - return completions; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -857,8 +613,8 @@ void RicWellPathExportCompletionDataFeatureImpl::sortAndExportCompletionsToFile( bool exportWelspec ) { // Sort completions based on grid they belong to - std::vector completionsForMainGrid = mainGridCompletions( completions ); - std::map> completionsForSubGrids = subGridsCompletions( completions ); + std::vector completionsForMainGrid = RicCompletionDataExportTools::mainGridCompletions( completions ); + std::map> completionsForSubGrids = RicCompletionDataExportTools::subGridsCompletions( completions ); if ( !completionsForMainGrid.empty() ) { @@ -935,18 +691,18 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatAndWpimultTables( // Print completion data QString gridName = gridCompletions.first; - exportCompdatTableUsingFormatter( formatter, gridName, completions ); + RicCompletionDataExportTools::exportCompdatTableUsingFormatter( formatter, gridName, completions ); if ( exportType == RicExportCompletionDataSettingsUi::CompdatExport::WPIMULT_AND_DEFAULT_CONNECTION_FACTORS ) { - exportWpimultTableUsingFormatter( formatter, gridName, completions ); + RicCompletionDataExportTools::exportWpimultTableUsingFormatter( formatter, gridName, completions ); } for ( auto& completion : completions ) { if ( completion.completionNumber().has_value() ) { - exportComplumpTableUsingFormatter( formatter, gridName, completions ); + RicCompletionDataExportTools::exportComplumpTableUsingFormatter( formatter, gridName, completions ); break; } } @@ -955,404 +711,6 @@ void RicWellPathExportCompletionDataFeatureImpl::exportCompdatAndWpimultTables( RiaLogging::info( std::format( "Successfully exported completion data to {}", exportFile->fileName() ) ); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicWellPathExportCompletionDataFeatureImpl::exportCompdatTableUsingFormatter( RifTextDataTableFormatter& formatter, - const QString& gridName, - const std::vector& completionData ) -{ - if ( gridName.isEmpty() ) - { - std::vector header = - { RifTextDataTableColumn( "WELL", "NAME" ), - RifTextDataTableColumn( "", "I" ), - RifTextDataTableColumn( "", "J" ), - RifTextDataTableColumn( "", "K1" ), - RifTextDataTableColumn( "", "K2" ), - RifTextDataTableColumn( "OPEN", "SHUT" ), - RifTextDataTableColumn( "SAT", "TAB" ), - RifTextDataTableColumn( "CONN", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), - RifTextDataTableColumn( "WELL", "DIA" ), - RifTextDataTableColumn( "KH", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), - RifTextDataTableColumn( "SKIN", "FACT" ), - RifTextDataTableColumn( "D", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), - RifTextDataTableColumn( "DIR", "PEN" ) }; - - formatter.header( header ); - formatter.keyword( "COMPDAT" ); - } - else - { - std::vector header = - { RifTextDataTableColumn( "WELL", "NAME" ), - RifTextDataTableColumn( "LGR", "NAME" ), - RifTextDataTableColumn( "", "I" ), - RifTextDataTableColumn( "", "J" ), - RifTextDataTableColumn( "", "K1" ), - RifTextDataTableColumn( "", "K2" ), - RifTextDataTableColumn( "OPEN", "SHUT" ), - RifTextDataTableColumn( "SAT", "TAB" ), - RifTextDataTableColumn( "CONN", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), - RifTextDataTableColumn( "WELL", "DIA" ), - RifTextDataTableColumn( "KH", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), - RifTextDataTableColumn( "SKIN", "FACT" ), - RifTextDataTableColumn( "D", "FACT", RifTextDataTableDoubleFormatting( RifTextDataTableDoubleFormat::RIF_SCIENTIFIC ) ), - RifTextDataTableColumn( "DIR", "PEN" ) }; - - formatter.header( header ); - formatter.keyword( "COMPDATL" ); - } - - RigCompletionData::CompletionType currentCompletionType = RigCompletionData::CompletionType::CT_UNDEFINED; - - for ( const RigCompletionData& data : completionData ) - { - if ( currentCompletionType != data.completionType() ) - { - // The completions are sorted by completion type, write out a heading when completion type changes - - QString txt; - if ( data.completionType() == RigCompletionData::CompletionType::FISHBONES ) txt = "Fishbones"; - if ( data.completionType() == RigCompletionData::CompletionType::FRACTURE ) txt = "Fracture"; - if ( data.completionType() == RigCompletionData::CompletionType::PERFORATION ) txt = "Perforation"; - - formatter.addOptionalComment( "---- Completions for completion type " + txt + " ----" ); - - currentCompletionType = data.completionType(); - } - - for ( const RigCompletionMetaData& metadata : data.metadata() ) - { - formatter.addOptionalComment( QString( "%1 : %2" ).arg( metadata.name ).arg( metadata.comment ) ); - } - - if ( data.transmissibility() == 0.0 || data.wpimult() == 0.0 ) - { - // Don't export completions without transmissibility - continue; - } - - formatter.add( data.wellName() ); - - if ( !gridName.isEmpty() ) - { - formatter.add( data.completionDataGridCell().lgrName() ); - } - - formatter.add( data.completionDataGridCell().localCellIndexI() + 1 ) - .add( data.completionDataGridCell().localCellIndexJ() + 1 ) - .add( data.completionDataGridCell().localCellIndexK() + 1 ) - .add( data.completionDataGridCell().localCellIndexK() + 1 ); - - formatter.add( "OPEN" ); - - formatter.addValueOrDefaultMarker( data.saturation(), RigCompletionData::defaultValue() ); - formatter.addValueOrDefaultMarker( data.transmissibility(), RigCompletionData::defaultValue() ); - formatter.addValueOrDefaultMarker( data.diameter(), RigCompletionData::defaultValue() ); - formatter.addValueOrDefaultMarker( data.kh(), RigCompletionData::defaultValue() ); - formatter.addValueOrDefaultMarker( data.skinFactor(), RigCompletionData::defaultValue() ); - formatter.addValueOrDefaultMarker( data.dFactor(), RigCompletionData::defaultValue() ); - - switch ( data.direction() ) - { - case RigCompletionData::CellDirection::DIR_I: - formatter.add( "'X'" ); - break; - case RigCompletionData::CellDirection::DIR_J: - formatter.add( "'Y'" ); - break; - case RigCompletionData::CellDirection::DIR_K: - default: - formatter.add( "'Z'" ); - break; - } - - formatter.rowCompleted(); - } - formatter.tableCompleted(); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicWellPathExportCompletionDataFeatureImpl::exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter, - const QString& gridName, - const std::vector& completionData ) -{ - if ( gridName.isEmpty() ) - { - std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), - RifTextDataTableColumn( "", "I" ), - RifTextDataTableColumn( "", "J" ), - RifTextDataTableColumn( "", "K1" ), - RifTextDataTableColumn( "", "K2" ), - RifTextDataTableColumn( "COMP", "NUM" ) }; - - formatter.header( header ); - formatter.keyword( "COMPLUMP" ); - } - else - { - std::vector header = { RifTextDataTableColumn( "WELL", "NAME" ), - RifTextDataTableColumn( "LGR", "NAME" ), - RifTextDataTableColumn( "", "I" ), - RifTextDataTableColumn( "", "J" ), - RifTextDataTableColumn( "", "K1" ), - RifTextDataTableColumn( "", "K2" ), - RifTextDataTableColumn( "COMP", "NUM" ) }; - - formatter.header( header ); - formatter.keyword( "COMPLMPL" ); - } - - for ( const RigCompletionData& data : completionData ) - { - if ( !data.completionNumber().has_value() || data.transmissibility() == 0.0 || data.wpimult() == 0.0 ) - { - // Don't export completions without transmissibility or completion number set - continue; - } - - formatter.add( data.wellName() ); - - if ( !gridName.isEmpty() ) - { - formatter.add( data.completionDataGridCell().lgrName() ); - } - - formatter.add( data.completionDataGridCell().localCellIndexI() + 1 ) - .add( data.completionDataGridCell().localCellIndexJ() + 1 ) - .add( data.completionDataGridCell().localCellIndexK() + 1 ) - .add( data.completionDataGridCell().localCellIndexK() + 1 ) - .add( data.completionNumber().value() ); - - formatter.rowCompleted(); - } - formatter.tableCompleted(); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicWellPathExportCompletionDataFeatureImpl::exportWpimultTableUsingFormatter( RifTextDataTableFormatter& formatter, - const QString& gridName, - const std::vector& completionData ) -{ - std::vector header; - - if ( gridName.isEmpty() ) - { - header = { - RifTextDataTableColumn( "Well" ), - RifTextDataTableColumn( "Mult" ), - RifTextDataTableColumn( "I" ), - RifTextDataTableColumn( "J" ), - RifTextDataTableColumn( "K" ), - }; - formatter.keyword( "WPIMULT" ); - } - else - { - header = { - RifTextDataTableColumn( "Well" ), - RifTextDataTableColumn( "LgrName" ), - RifTextDataTableColumn( "Mult" ), - RifTextDataTableColumn( "I" ), - RifTextDataTableColumn( "J" ), - RifTextDataTableColumn( "K" ), - }; - formatter.keyword( "WPIMULTL" ); - } - formatter.header( header ); - - for ( auto& completion : completionData ) - { - if ( completion.wpimult() == 0.0 || RigCompletionData::isDefaultValue( completion.wpimult() ) ) - { - continue; - } - - formatter.add( completion.wellName() ); - - if ( !gridName.isEmpty() ) - { - formatter.add( completion.completionDataGridCell().lgrName() ); - } - - formatter.add( completion.wpimult() ); - - formatter.add( completion.completionDataGridCell().localCellIndexI() + 1 ) - .add( completion.completionDataGridCell().localCellIndexJ() + 1 ) - .add( completion.completionDataGridCell().localCellIndexK() + 1 ); - formatter.rowCompleted(); - } - - formatter.tableCompleted(); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -std::vector - RicWellPathExportCompletionDataFeatureImpl::generatePerforationsCompdatValues( const RimWellPath* wellPath, - const std::vector& intervals, - const RicExportCompletionDataSettingsUi& settings, - const std::optional& exportDate ) -{ - RiaDefines::EclipseUnitSystem unitSystem = settings.caseToApply->eclipseCaseData()->unitsType(); - - std::vector completionData; - - const RigActiveCellInfo* activeCellInfo = - settings.caseToApply->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); - - auto wellPathGeometry = wellPath->wellPathGeometry(); - if ( !wellPathGeometry ) return completionData; - - const RimNonDarcyPerforationParameters* nonDarcyParameters = wellPath->perforationIntervalCollection()->nonDarcyParameters(); - - if ( wellPath->perforationIntervalCollection()->isChecked() ) - { - for ( const RimPerforationInterval* interval : intervals ) - { - if ( !interval->isChecked() ) continue; - if ( exportDate.has_value() && !interval->isActiveOnDate( *exportDate ) ) continue; - - std::pair, std::vector> perforationPointsAndMD = - wellPathGeometry->clippedPointSubset( interval->startMD(), interval->endMD() ); - - std::vector intersectedCells = - RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( settings.caseToApply->eclipseCaseData(), - wellPath->name(), - perforationPointsAndMD.first, - perforationPointsAndMD.second ); - - RicPerforationCellFilterEvaluator filterEval( interval->cellFilter(), settings.caseToApply ); - - const size_t completionCountBefore = completionData.size(); - - for ( auto& cell : intersectedCells ) - { - bool cellIsActive = activeCellInfo->isActive( ReservoirCellIndex( cell.globCellIndex ) ); - if ( !cellIsActive ) continue; - if ( !filterEval.includesGlobalCell( cell.globCellIndex ) ) continue; - - RigCompletionData completion( wellPath->completionSettings()->wellNameForExport(), - RigCompletionDataGridCell( cell.globCellIndex, settings.caseToApply->mainGrid() ), - cell.startMD ); - - RigCompletionData::CellDirection direction = - RicTransmissibilityCalculator::calculateCellMainDirection( settings.caseToApply, - cell.globCellIndex, - cell.intersectionLengthsInCellCS ); - - double transmissibility = 0.0; - double kh = RigCompletionData::defaultValue(); - double dFactor = RigCompletionData::defaultValue(); - - { - auto transmissibilityData = - RicTransmissibilityCalculator::calculateTransmissibilityData( settings.caseToApply, - wellPath, - cell.intersectionLengthsInCellCS, - interval->skinFactor(), - interval->diameter( unitSystem ) / 2, - cell.globCellIndex, - settings.useLateralNTG ); - - transmissibility = transmissibilityData.connectionFactor(); - kh = transmissibilityData.kh(); - - if ( nonDarcyParameters->nonDarcyFlowType() == RimNonDarcyPerforationParameters::NON_DARCY_COMPUTED ) - { - const double effectiveH = transmissibilityData.effectiveH(); - - const double effectivePermeability = - nonDarcyParameters->gridPermeabilityScalingFactor() * transmissibilityData.effectiveK(); - - dFactor = - RicTransmissibilityCalculator::calculateDFactor( settings.caseToApply, - effectiveH, - cell.globCellIndex, - wellPath->perforationIntervalCollection()->nonDarcyParameters(), - effectivePermeability ); - } - } - - completion.setTransAndWPImultBackgroundDataFromPerforation( transmissibility, - interval->skinFactor(), - interval->diameter( unitSystem ), - dFactor, - kh, - direction ); - completion.setDepthRange( cell.startMD, cell.endMD ); - if ( interval->completionNumber() > 0 ) completion.setCompletionNumber( interval->completionNumber() ); - - completion.addMetadata( "Perforation Completion", - QString( "MD In: %1 - MD Out: %2" ).arg( cell.startMD ).arg( cell.endMD ) + - QString( " Transmissibility: " ) + QString::number( transmissibility ) ); - completion.setSourcePdmObject( interval ); - completionData.push_back( completion ); - } - - if ( filterEval.isEnabled() && completionData.size() == completionCountBefore && filterEval.rejectedCellCount() > 0 ) - { - RiaLogging::info( - std::format( "Perforation '{}' on well '{}': all {} intersected cells suppressed by attached cell filter '{}'.", - interval->name(), - wellPath->name(), - filterEval.rejectedCellCount(), - interval->cellFilter() ? interval->cellFilter()->fullName() : QString() ) ); - } - } - } - - if ( nonDarcyParameters->nonDarcyFlowType() == RimNonDarcyPerforationParameters::NON_DARCY_USER_DEFINED ) - { - // Compute D-factor for completion as - // D_cell = D_well * Sum_Tran_cells / Tran_cell - // - // See https://github.com/OPM/ResInsight/issues/7450 - - double accumulatedTransmissibility = 0.0; - for ( const auto& completion : completionData ) - { - accumulatedTransmissibility += completion.transmissibility(); - } - - double userDefinedDFactor = nonDarcyParameters->userDefinedDFactor(); - for ( auto& completion : completionData ) - { - double dFactorForCompletion = userDefinedDFactor * accumulatedTransmissibility / completion.transmissibility(); - completion.setDFactor( dFactorForCompletion ); - } - } - - return completionData; -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicWellPathExportCompletionDataFeatureImpl::appendCompletionData( std::map>* completionData, - const std::vector& completionsToAppend ) -{ - for ( const auto& completion : completionsToAppend ) - { - auto it = completionData->find( completion.completionDataGridCell().globalCellIndex() ); - if ( it != completionData->end() ) - { - it->second.push_back( completion ); - } - else - { - completionData->insert( std::pair>( completion.completionDataGridCell().globalCellIndex(), - std::vector{ completion } ) ); - } - } -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1399,23 +757,6 @@ std::pair RicWellPathExportCompletionDataFeatureImpl::wellPa return std::make_pair( cvf::UNDEFINED_DOUBLE, cvf::Vec2i() ); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimWellPath* RicWellPathExportCompletionDataFeatureImpl::topLevelWellPath( const RigCompletionData& completion ) -{ - if ( completion.sourcePdmObject() ) - { - auto parentWellPath = completion.sourcePdmObject()->firstAncestorOrThisOfType(); - if ( parentWellPath ) - { - return parentWellPath->topLevelWellPath(); - } - } - - return nullptr; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1502,12 +843,12 @@ std::vector if ( exportSettings.includePerforations ) { std::vector perforationCompletionData = - generatePerforationsCompdatValues( wellPathLateral, - wellPathLateral->perforationIntervalCollection()->perforations(), - exportSettings, - exportDate ); + RicCompletionDataExportTools::generatePerforationsCompdatValues( wellPathLateral, + wellPathLateral->perforationIntervalCollection()->perforations(), + exportSettings, + exportDate ); - appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, perforationCompletionData ); + RicCompletionDataExportTools::appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, perforationCompletionData ); } if ( exportSettings.includeFishbones ) @@ -1519,7 +860,7 @@ std::vector RicFishbonesTransmissibilityCalculationFeatureImp::generateFishboneCompdatValuesUsingAdjustedCellVolume( wellPathLateral, exportSettings ); - appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fishbonesCompletionData ); + RicCompletionDataExportTools::appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fishbonesCompletionData ); } if ( exportSettings.includeFractures() ) @@ -1538,13 +879,13 @@ std::vector exportSettings.transScalingWBHPSource(), exportSettings.transScalingWBHP() ) ); - appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fractureCompletionData ); + RicCompletionDataExportTools::appendCompletionData( &completionsPerEclipseCellAllCompletionTypes, fractureCompletionData ); } } for ( auto& data : completionsPerEclipseCellAllCompletionTypes ) { - completions.push_back( combineEclipseCellCompletions( data.second, exportSettings ) ); + completions.push_back( RicCompletionDataExportTools::combineEclipseCellCompletions( data.second, exportSettings ) ); } // Order completions with each well's mainbore connections first, followed by each lateral's diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h index b5e9e13923..4c826f2e0a 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h @@ -80,23 +80,7 @@ public: static std::optional exportDateForTimeStep( const RimEclipseCase* eclipseCase, size_t timeStepIndex ); - // Completion number for a cell with several contributions: the number with the largest summed MD overlap - // (endMD - startMD) wins, ties go to the lowest number. Independent of contribution order. - static std::optional completionNumberForCell( const std::vector& completions ); - private: - static std::vector generatePerforationsCompdatValues( const RimWellPath* wellPath, - const std::vector& intervals, - const RicExportCompletionDataSettingsUi& settings, - const std::optional& exportDate = std::nullopt ); - - static RigCompletionData combineEclipseCellCompletions( const std::vector& completions, - const RicExportCompletionDataSettingsUi& settings ); - - static std::vector mainGridCompletions( const std::vector& allCompletions ); - - static std::map> subGridsCompletions( const std::vector& allCompletions ); - static void exportWellPathFractureReport( RimEclipseCase* sourceCase, QFilePtr exportFile, const std::vector& wellPathFractureReportItems ); @@ -125,22 +109,5 @@ private: RicExportCompletionDataSettingsUi::CompdatExportType exportType, bool exportDataSourceAsComment ); - static void exportCompdatTableUsingFormatter( RifTextDataTableFormatter& formatter, - const QString& gridName, - const std::vector& completionData ); - - static void exportComplumpTableUsingFormatter( RifTextDataTableFormatter& formatter, - const QString& gridName, - const std::vector& completionData ); - - static void exportWpimultTableUsingFormatter( RifTextDataTableFormatter& formatter, - const QString& gridName, - const std::vector& completionData ); - - static void appendCompletionData( std::map>* completionData, - const std::vector& data ); - static void exportCarfinForTemporaryLgrs( const RimEclipseCase* sourceCase, const QString& folder ); - - static RimWellPath* topLevelWellPath( const RigCompletionData& completion ); };