diff --git a/ApplicationLibCode/Application/Tools/RiaEclipseUnitTools.cpp b/ApplicationLibCode/Application/Tools/RiaEclipseUnitTools.cpp index 45f13f274c..c7c10636d5 100644 --- a/ApplicationLibCode/Application/Tools/RiaEclipseUnitTools.cpp +++ b/ApplicationLibCode/Application/Tools/RiaEclipseUnitTools.cpp @@ -19,8 +19,8 @@ #include "RiaEclipseUnitTools.h" #include "cafAppEnum.h" - #include "cvfAssert.h" + #include //-------------------------------------------------------------------------------------------------- @@ -28,7 +28,6 @@ //-------------------------------------------------------------------------------------------------- double RiaEclipseUnitTools::darcysConstant( RiaDefines::EclipseUnitSystem unitSystem ) { - // See "Cartesian transmissibility calculations" in the "Eclipse Technical Description" // CDARCY Darcys constant // = 0.00852702 (E300); 0.008527 (ECLIPSE 100) (METRIC) // = 0.00112712 (E300); 0.001127 (ECLIPSE 100) (FIELD) diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake index 4ca51a0952..f8193ed570 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/CompletionExportCommands/CMakeLists_files.cmake @@ -22,6 +22,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportMswTableData.h ${CMAKE_CURRENT_LIST_DIR}/RicMswTableDataTools.h ${CMAKE_CURRENT_LIST_DIR}/RicScheduleDataGenerator.h + ${CMAKE_CURRENT_LIST_DIR}/RicTransmissibilityCalculator.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -48,6 +49,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportMswTableData.cpp ${CMAKE_CURRENT_LIST_DIR}/RicMswTableDataTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RicScheduleDataGenerator.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicTransmissibilityCalculator.cpp ) list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicFishbonesTransmissibilityCalculationFeatureImp.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicFishbonesTransmissibilityCalculationFeatureImp.cpp index c63a2345c0..33cc018e36 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicFishbonesTransmissibilityCalculationFeatureImp.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicFishbonesTransmissibilityCalculationFeatureImp.cpp @@ -23,6 +23,7 @@ #include "RicMswCompletions.h" #include "RicMswExportInfo.h" #include "RicMswSegment.h" +#include "RicTransmissibilityCalculator.h" #include "RicWellPathExportCompletionDataFeatureImpl.h" #include "RicWellPathExportMswTableData.h" @@ -113,9 +114,9 @@ std::vector RicFishbonesTransmissibilityCalculationFeatureImp } else { - mainBoreDirection = RicWellPathExportCompletionDataFeatureImpl::calculateCellMainDirection( settings.caseToApply, - globalCellIndex, - wellBorePart.lengthsInCell ); + mainBoreDirection = RicTransmissibilityCalculator::calculateCellMainDirection( settings.caseToApply, + globalCellIndex, + wellBorePart.lengthsInCell ); } } @@ -137,13 +138,13 @@ std::vector RicFishbonesTransmissibilityCalculationFeatureImp { // No change in transmissibility for main bore auto transmissibilityAndPermeability = - RicWellPathExportCompletionDataFeatureImpl::calculateTransmissibilityData( settings.caseToApply, - wellPath, - wellBorePart.lengthsInCell, - wellBorePart.skinFactor, - wellBorePart.wellRadius, - globalCellIndex, - settings.useLateralNTG ); + RicTransmissibilityCalculator::calculateTransmissibilityData( settings.caseToApply, + wellPath, + wellBorePart.lengthsInCell, + wellBorePart.skinFactor, + wellBorePart.wellRadius, + globalCellIndex, + settings.useLateralNTG ); transmissibility = transmissibilityAndPermeability.connectionFactor(); kh = transmissibilityAndPermeability.kh(); @@ -151,24 +152,22 @@ std::vector RicFishbonesTransmissibilityCalculationFeatureImp else { // Adjust transmissibility for fishbone laterals - auto transmissibilityAndPermeability = - RicWellPathExportCompletionDataFeatureImpl::calculateTransmissibilityData( settings.caseToApply, - wellPath, - wellBorePart.lengthsInCell, - wellBorePart.skinFactor, - wellBorePart.wellRadius, - globalCellIndex, - settings.useLateralNTG, - numberOfLaterals, - mainBoreDirection ); + auto transmissibilityAndPermeability = RicTransmissibilityCalculator::calculateTransmissibilityData( settings.caseToApply, + wellPath, + wellBorePart.lengthsInCell, + wellBorePart.skinFactor, + wellBorePart.wellRadius, + globalCellIndex, + settings.useLateralNTG, + numberOfLaterals, + mainBoreDirection ); transmissibility = transmissibilityAndPermeability.connectionFactor(); kh = transmissibilityAndPermeability.kh(); } - auto direction = RicWellPathExportCompletionDataFeatureImpl::calculateCellMainDirection( settings.caseToApply, - globalCellIndex, - wellBorePart.lengthsInCell ); + auto direction = + RicTransmissibilityCalculator::calculateCellMainDirection( settings.caseToApply, globalCellIndex, wellBorePart.lengthsInCell ); completion.setTransAndWPImultBackgroundDataFromFishbone( transmissibility, wellBorePart.skinFactor, diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicTransmissibilityCalculator.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicTransmissibilityCalculator.cpp new file mode 100644 index 0000000000..157acf725b --- /dev/null +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicTransmissibilityCalculator.cpp @@ -0,0 +1,316 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 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 "RicTransmissibilityCalculator.h" + +#include + +#include "RiaEclipseUnitTools.h" +#include "RiaFractureDefines.h" + +#include "RigCaseCellResultsData.h" +#include "RigEclipseCaseData.h" +#include "RigEclipseResultAddress.h" +#include "RigMainGrid.h" +#include "RigPerforationTransmissibilityEquations.h" +#include "RigResultAccessorFactory.h" +#include "RigTransmissibilityEquations.h" + +#include "RimEclipseCase.h" +#include "RimNonDarcyPerforationParameters.h" +#include "RimWellPath.h" + +namespace internal +{ + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaDefines::PorosityModelType porosityModel( RimEclipseCase* eclipseCase ) +{ + if ( eclipseCase && eclipseCase->eclipseCaseData() && eclipseCase->eclipseCaseData()->mainGrid() && + eclipseCase->eclipseCaseData()->mainGrid()->isDualPorosity() ) + { + return RiaDefines::PorosityModelType::FRACTURE_MODEL; + } + return RiaDefines::PorosityModelType::MATRIX_MODEL; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::ref loadResultAccessor( RimEclipseCase* eclipseCase, const QString& resultName ) +{ + if ( !eclipseCase || !eclipseCase->eclipseCaseData() ) return nullptr; + + auto porModel = porosityModel( eclipseCase ); + + auto* results = eclipseCase->results( porModel ); + if ( !results ) return nullptr; + + results->ensureKnownResultLoaded( RigEclipseResultAddress( resultName ) ); + return RigResultAccessorFactory::createFromResultAddress( eclipseCase->eclipseCaseData(), 0, porModel, 0, RigEclipseResultAddress( resultName ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +struct CellProperties +{ + double dx, dy, dz; + double permx, permy, permz; +}; + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional loadCellProperties( RimEclipseCase* eclipseCase, size_t globalCellIndex ) +{ + auto dxAcc = loadResultAccessor( eclipseCase, "DX" ); + auto dyAcc = loadResultAccessor( eclipseCase, "DY" ); + auto dzAcc = loadResultAccessor( eclipseCase, "DZ" ); + auto permxAcc = loadResultAccessor( eclipseCase, "PERMX" ); + auto permyAcc = loadResultAccessor( eclipseCase, "PERMY" ); + auto permzAcc = loadResultAccessor( eclipseCase, "PERMZ" ); + + if ( dxAcc.isNull() || dyAcc.isNull() || dzAcc.isNull() || permxAcc.isNull() || permyAcc.isNull() || permzAcc.isNull() ) + { + return std::nullopt; + } + + CellProperties props; + props.dx = dxAcc->cellScalarGlobIdx( globalCellIndex ); + props.dy = dyAcc->cellScalarGlobIdx( globalCellIndex ); + props.dz = dzAcc->cellScalarGlobIdx( globalCellIndex ); + props.permx = permxAcc->cellScalarGlobIdx( globalCellIndex ); + props.permy = permyAcc->cellScalarGlobIdx( globalCellIndex ); + props.permz = permzAcc->cellScalarGlobIdx( globalCellIndex ); + return props; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double loadNtg( RimEclipseCase* eclipseCase, size_t globalCellIndex ) +{ + auto ntgAcc = loadResultAccessor( eclipseCase, "NTG" ); + if ( ntgAcc.notNull() ) return ntgAcc->cellScalarGlobIdx( globalCellIndex ); + return 1.0; +} + +} // namespace internal + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigCompletionData::CellDirection RicTransmissibilityCalculator::calculateCellMainDirection( RimEclipseCase* eclipseCase, + size_t globalCellIndex, + const cvf::Vec3d& lengthsInCell ) +{ + if ( !eclipseCase || !eclipseCase->eclipseCaseData() ) return RigCompletionData::CellDirection::DIR_K; + + auto props = internal::loadCellProperties( eclipseCase, globalCellIndex ); + if ( !props ) return RigCompletionData::CellDirection::DIR_K; + + double xLengthFraction = fabs( lengthsInCell.x() / props->dx ); + double yLengthFraction = fabs( lengthsInCell.y() / props->dy ); + double zLengthFraction = fabs( lengthsInCell.z() / props->dz ); + + if ( xLengthFraction > yLengthFraction && xLengthFraction > zLengthFraction ) + { + return RigCompletionData::CellDirection::DIR_I; + } + else if ( yLengthFraction > xLengthFraction && yLengthFraction > zLengthFraction ) + { + return RigCompletionData::CellDirection::DIR_J; + } + else + { + return RigCompletionData::CellDirection::DIR_K; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TransmissibilityData RicTransmissibilityCalculator::calculateTransmissibilityData( RimEclipseCase* eclipseCase, + const RimWellPath* wellPath, + const cvf::Vec3d& internalCellLengths, + double skinFactor, + double wellRadius, + size_t globalCellIndex, + bool useLateralNTG, + size_t volumeScaleConstant, + RigCompletionData::CellDirection directionForVolumeScaling ) +{ + if ( !eclipseCase || !eclipseCase->eclipseCaseData() || !wellPath ) return TransmissibilityData(); + + auto props = internal::loadCellProperties( eclipseCase, globalCellIndex ); + if ( !props ) + { + return TransmissibilityData(); + } + + double ntg = internal::loadNtg( eclipseCase, globalCellIndex ); + double latNtg = useLateralNTG ? ntg : 1.0; + + const double totalKh = RigTransmissibilityEquations::totalKh( props->permx, props->permy, props->permz, internalCellLengths, latNtg, ntg ); + const double effectiveK = + RigTransmissibilityEquations::effectiveK( props->permx, props->permy, props->permz, internalCellLengths, latNtg, ntg ); + const double effectiveH = RigTransmissibilityEquations::effectiveH( internalCellLengths, latNtg, ntg ); + + double darcy = RiaEclipseUnitTools::darcysConstant( wellPath->unitSystem() ); + + double dx = props->dx; + double dy = props->dy; + double dz = props->dz; + if ( volumeScaleConstant != 1 ) + { + if ( directionForVolumeScaling == RigCompletionData::CellDirection::DIR_I ) dx = dx / volumeScaleConstant; + if ( directionForVolumeScaling == RigCompletionData::CellDirection::DIR_J ) dy = dy / volumeScaleConstant; + if ( directionForVolumeScaling == RigCompletionData::CellDirection::DIR_K ) dz = dz / volumeScaleConstant; + } + + const double transx = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( internalCellLengths.x() * latNtg, + props->permy, + props->permz, + dy, + dz, + wellRadius, + skinFactor, + darcy ); + const double transy = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( internalCellLengths.y() * latNtg, + props->permx, + props->permz, + dx, + dz, + wellRadius, + skinFactor, + darcy ); + const double transz = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( internalCellLengths.z() * ntg, + props->permy, + props->permx, + dy, + dx, + wellRadius, + skinFactor, + darcy ); + + const double totalConnectionFactor = RigTransmissibilityEquations::totalConnectionFactor( transx, transy, transz ); + + TransmissibilityData trData; + trData.setData( effectiveH, effectiveK, totalConnectionFactor, totalKh ); + return trData; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RicTransmissibilityCalculator::calculateDFactor( RimEclipseCase* eclipseCase, + double effectiveH, + size_t globalCellIndex, + const RimNonDarcyPerforationParameters* nonDarcyParameters, + const double effectivePermeability ) +{ + using EQ = RigPerforationTransmissibilityEquations; + + if ( !eclipseCase || !eclipseCase->eclipseCaseData() ) + { + return std::numeric_limits::infinity(); + } + + double porosity = 0.0; + { + auto poroAcc = internal::loadResultAccessor( eclipseCase, "PORO" ); + if ( poroAcc.notNull() ) + { + porosity = poroAcc->cellScalar( globalCellIndex ); + } + } + + const double betaFactor = EQ::betaFactor( nonDarcyParameters->inertialCoefficientBeta0(), + effectivePermeability, + nonDarcyParameters->permeabilityScalingFactor(), + porosity, + nonDarcyParameters->porosityScalingFactor() ); + + const double alpha = RiaDefines::nonDarcyFlowAlpha( eclipseCase->eclipseCaseData()->unitsType() ); + + return EQ::dFactor( alpha, + betaFactor, + effectivePermeability, + effectiveH, + nonDarcyParameters->wellRadius(), + nonDarcyParameters->relativeGasDensity(), + nonDarcyParameters->gasViscosity() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RicTransmissibilityCalculator::calculateTransmissibilityAsEclipseDoes( RimEclipseCase* eclipseCase, + double skinFactor, + double wellRadius, + size_t globalCellIndex, + RigCompletionData::CellDirection direction ) +{ + if ( !eclipseCase || !eclipseCase->eclipseCaseData() ) return cvf::UNDEFINED_DOUBLE; + + auto props = internal::loadCellProperties( eclipseCase, globalCellIndex ); + if ( !props ) return cvf::UNDEFINED_DOUBLE; + + double ntg = internal::loadNtg( eclipseCase, globalCellIndex ); + double darcy = RiaEclipseUnitTools::darcysConstant( eclipseCase->eclipseCaseData()->unitsType() ); + + double trans = cvf::UNDEFINED_DOUBLE; + if ( direction == RigCompletionData::CellDirection::DIR_I ) + { + trans = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( props->dx, + props->permy, + props->permz, + props->dy, + props->dz, + wellRadius, + skinFactor, + darcy ); + } + else if ( direction == RigCompletionData::CellDirection::DIR_J ) + { + trans = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( props->dy, + props->permx, + props->permz, + props->dx, + props->dz, + wellRadius, + skinFactor, + darcy ); + } + else if ( direction == RigCompletionData::CellDirection::DIR_K ) + { + trans = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( props->dz * ntg, + props->permy, + props->permx, + props->dy, + props->dx, + wellRadius, + skinFactor, + darcy ); + } + + return trans; +} diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicTransmissibilityCalculator.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicTransmissibilityCalculator.h new file mode 100644 index 0000000000..1740786837 --- /dev/null +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicTransmissibilityCalculator.h @@ -0,0 +1,101 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 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. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RigCompletionData.h" + +#include "cvfVector3.h" + +class RimEclipseCase; +class RimNonDarcyPerforationParameters; +class RimWellPath; + +//================================================================================================== +/// +//================================================================================================== +class TransmissibilityData +{ +public: + TransmissibilityData() + : m_isValid( false ) + , m_effectiveH( 0.0 ) + , m_effectiveK( 0.0 ) + , m_connectionFactor( 0.0 ) + , m_kh( 0.0 ) + { + } + + bool isValid() const { return m_isValid; } + + void setData( double effectiveH, double effectiveK, double connectionFactor, double kh ) + { + m_isValid = true; + + m_effectiveH = effectiveH; + m_effectiveK = effectiveK; + m_connectionFactor = connectionFactor; + m_kh = kh; + } + + double effectiveH() const { return m_effectiveH; } + + double effectiveK() const { return m_effectiveK; } + double connectionFactor() const { return m_connectionFactor; } + double kh() const { return m_kh; } + +private: + bool m_isValid; + double m_effectiveH; + double m_effectiveK; + double m_connectionFactor; + double m_kh; +}; + +//================================================================================================== +/// +//================================================================================================== +class RicTransmissibilityCalculator +{ +public: + static RigCompletionData::CellDirection + calculateCellMainDirection( RimEclipseCase* eclipseCase, size_t globalCellIndex, const cvf::Vec3d& lengthsInCell ); + + static TransmissibilityData + calculateTransmissibilityData( RimEclipseCase* eclipseCase, + const RimWellPath* wellPath, + const cvf::Vec3d& internalCellLengths, + double skinFactor, + double wellRadius, + size_t globalCellIndex, + bool useLateralNTG, + size_t volumeScaleConstant = 1, + RigCompletionData::CellDirection directionForVolumeScaling = RigCompletionData::CellDirection::DIR_I ); + + static double calculateDFactor( RimEclipseCase* eclipseCase, + double effectiveH, + size_t globalCellIndex, + const RimNonDarcyPerforationParameters* nonDarcyParameters, + const double effectivePermeability ); + + static double calculateTransmissibilityAsEclipseDoes( RimEclipseCase* eclipseCase, + double skinFactor, + double wellRadius, + size_t globalCellIndex, + RigCompletionData::CellDirection direction ); +}; diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp index ed9e50a653..7796c503df 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.cpp @@ -18,9 +18,6 @@ #include "RicWellPathExportCompletionDataFeatureImpl.h" -#include "RiaEclipseUnitTools.h" -#include "RiaFilePathTools.h" -#include "RiaFractureDefines.h" #include "RiaLogging.h" #include "RiaPreferencesSystem.h" #include "RiaWeightedMeanCalculator.h" @@ -28,9 +25,9 @@ #include "ExportCommands/RicExportLgrFeature.h" #include "RicExportCompletionDataSettingsUi.h" -#include "RicExportFeatureImpl.h" #include "RicExportFractureCompletionsImpl.h" #include "RicFishbonesTransmissibilityCalculationFeatureImp.h" +#include "RicTransmissibilityCalculator.h" #include "RicWellPathExportCompletionsFileTools.h" #include "RicWellPathExportMswCompletionsImpl.h" #include "RicWellPathFractureReportItem.h" @@ -40,46 +37,26 @@ #include "RifThermalToStimPlanFractureXmlOutput.h" #include "RigActiveCellInfo.h" -#include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" -#include "RigEclipseResultAddress.h" #include "RigMainGrid.h" -#include "RigPerforationTransmissibilityEquations.h" -#include "RigResultAccessorFactory.h" -#include "RigTransmissibilityEquations.h" -#include "RigVirtualPerforationTransmissibilities.h" -#include "Well/RigWellLogExtractionTools.h" #include "Well/RigWellLogExtractor.h" #include "Well/RigWellPath.h" #include "Well/RigWellPathIntersectionTools.h" #include "RimEclipseCase.h" -#include "RimFileWellPath.h" -#include "RimFishbones.h" #include "RimFishbonesCollection.h" -#include "RimFractureTemplate.h" #include "RimNonDarcyPerforationParameters.h" #include "RimPerforationCollection.h" #include "RimPerforationInterval.h" -#include "RimProject.h" -#include "RimSimWellInView.h" #include "RimThermalFractureTemplate.h" #include "RimWellPath.h" -#include "RimWellPathCollection.h" #include "RimWellPathCompletions.h" #include "RimWellPathFracture.h" #include "RimWellPathFractureCollection.h" -#include "RimWellPathValve.h" #include "Riu3DMainWindowTools.h" -#include "RiuMainWindow.h" -#include "cafPdmUiPropertyViewDialog.h" #include "cafProgressInfo.h" -#include "cafSelectionManager.h" -#include "cafUtils.h" - -#include "cvfPlane.h" #include @@ -551,11 +528,11 @@ RigCompletionData RicWellPathExportCompletionDataFeatureImpl::combineEclipseCell { // calculate trans for main bore - but as Eclipse will do it! double transmissibilityEclipseCalculation = - RicWellPathExportCompletionDataFeatureImpl::calculateTransmissibilityAsEclipseDoes( settings.caseToApply(), - combinedSkinFactor, - combinedDiameter / 2, - cellIndexIJK.globalCellIndex(), - cellDirection ); + RicTransmissibilityCalculator::calculateTransmissibilityAsEclipseDoes( settings.caseToApply(), + combinedSkinFactor, + combinedDiameter / 2, + cellIndexIJK.globalCellIndex(), + cellDirection ); double defaultKh = RigCompletionData::defaultValue(); double wpimult = combinedTrans / transmissibilityEclipseCalculation; @@ -1201,20 +1178,23 @@ std::vector cell.startMD ); RigCompletionData::CellDirection direction = - calculateCellMainDirection( settings.caseToApply, cell.globCellIndex, cell.intersectionLengthsInCellCS ); + RicTransmissibilityCalculator::calculateCellMainDirection( settings.caseToApply, + cell.globCellIndex, + cell.intersectionLengthsInCellCS ); double transmissibility = 0.0; double kh = RigCompletionData::defaultValue(); double dFactor = RigCompletionData::defaultValue(); { - auto transmissibilityData = calculateTransmissibilityData( settings.caseToApply, - wellPath, - cell.intersectionLengthsInCellCS, - interval->skinFactor(), - interval->diameter( unitSystem ) / 2, - cell.globCellIndex, - settings.useLateralNTG ); + 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(); @@ -1226,11 +1206,12 @@ std::vector const double effectivePermeability = nonDarcyParameters->gridPermeabilityScalingFactor() * transmissibilityData.effectiveK(); - dFactor = calculateDFactor( settings.caseToApply, - effectiveH, - cell.globCellIndex, - wellPath->perforationIntervalCollection()->nonDarcyParameters(), - effectivePermeability ); + dFactor = + RicTransmissibilityCalculator::calculateDFactor( settings.caseToApply, + effectiveH, + cell.globCellIndex, + wellPath->perforationIntervalCollection()->nonDarcyParameters(), + effectivePermeability ); } } @@ -1297,330 +1278,6 @@ void RicWellPathExportCompletionDataFeatureImpl::appendCompletionData( std::map< } } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RigCompletionData::CellDirection RicWellPathExportCompletionDataFeatureImpl::calculateCellMainDirection( RimEclipseCase* eclipseCase, - size_t globalCellIndex, - const cvf::Vec3d& lengthsInCell ) -{ - RigEclipseCaseData* eclipseCaseData = eclipseCase->eclipseCaseData(); - - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DX" ) ); - cvf::ref dxAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DX" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DY" ) ); - cvf::ref dyAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DY" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DZ" ) ); - cvf::ref dzAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DZ" ) ); - - double xLengthFraction = fabs( lengthsInCell.x() / dxAccessObject->cellScalarGlobIdx( globalCellIndex ) ); - double yLengthFraction = fabs( lengthsInCell.y() / dyAccessObject->cellScalarGlobIdx( globalCellIndex ) ); - double zLengthFraction = fabs( lengthsInCell.z() / dzAccessObject->cellScalarGlobIdx( globalCellIndex ) ); - - if ( xLengthFraction > yLengthFraction && xLengthFraction > zLengthFraction ) - { - return RigCompletionData::CellDirection::DIR_I; - } - else if ( yLengthFraction > xLengthFraction && yLengthFraction > zLengthFraction ) - { - return RigCompletionData::CellDirection::DIR_J; - } - else - { - return RigCompletionData::CellDirection::DIR_K; - } -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -TransmissibilityData - RicWellPathExportCompletionDataFeatureImpl::calculateTransmissibilityData( RimEclipseCase* eclipseCase, - const RimWellPath* wellPath, - const cvf::Vec3d& internalCellLengths, - double skinFactor, - double wellRadius, - size_t globalCellIndex, - bool useLateralNTG, - size_t volumeScaleConstant, - RigCompletionData::CellDirection directionForVolumeScaling ) -{ - RigEclipseCaseData* eclipseCaseData = eclipseCase->eclipseCaseData(); - - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DX" ) ); - cvf::ref dxAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DX" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DY" ) ); - cvf::ref dyAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DY" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DZ" ) ); - cvf::ref dzAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DZ" ) ); - - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PERMX" ) ); - cvf::ref permxAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PERMX" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PERMY" ) ); - cvf::ref permyAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PERMY" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PERMZ" ) ); - cvf::ref permzAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PERMZ" ) ); - - if ( dxAccessObject.isNull() || dyAccessObject.isNull() || dzAccessObject.isNull() || permxAccessObject.isNull() || - permyAccessObject.isNull() || permzAccessObject.isNull() ) - { - return TransmissibilityData(); - } - - double ntg = 1.0; - { - // Trigger loading from file - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "NTG" ) ); - - cvf::ref ntgAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "NTG" ) ); - - if ( ntgAccessObject.notNull() ) - { - ntg = ntgAccessObject->cellScalarGlobIdx( globalCellIndex ); - } - } - double latNtg = useLateralNTG ? ntg : 1.0; - - double dx = dxAccessObject->cellScalarGlobIdx( globalCellIndex ); - double dy = dyAccessObject->cellScalarGlobIdx( globalCellIndex ); - double dz = dzAccessObject->cellScalarGlobIdx( globalCellIndex ); - double permx = permxAccessObject->cellScalarGlobIdx( globalCellIndex ); - double permy = permyAccessObject->cellScalarGlobIdx( globalCellIndex ); - double permz = permzAccessObject->cellScalarGlobIdx( globalCellIndex ); - - const double totalKh = RigTransmissibilityEquations::totalKh( permx, permy, permz, internalCellLengths, latNtg, ntg ); - - const double effectiveK = RigTransmissibilityEquations::effectiveK( permx, permy, permz, internalCellLengths, latNtg, ntg ); - const double effectiveH = RigTransmissibilityEquations::effectiveH( internalCellLengths, latNtg, ntg ); - - double darcy = RiaEclipseUnitTools::darcysConstant( wellPath->unitSystem() ); - - if ( volumeScaleConstant != 1 ) - { - if ( directionForVolumeScaling == RigCompletionData::CellDirection::DIR_I ) dx = dx / volumeScaleConstant; - if ( directionForVolumeScaling == RigCompletionData::CellDirection::DIR_J ) dy = dy / volumeScaleConstant; - if ( directionForVolumeScaling == RigCompletionData::CellDirection::DIR_K ) dz = dz / volumeScaleConstant; - } - - const double transx = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( internalCellLengths.x() * latNtg, - permy, - permz, - dy, - dz, - wellRadius, - skinFactor, - darcy ); - const double transy = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( internalCellLengths.y() * latNtg, - permx, - permz, - dx, - dz, - wellRadius, - skinFactor, - darcy ); - const double transz = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( internalCellLengths.z() * ntg, - permy, - permx, - dy, - dx, - wellRadius, - skinFactor, - darcy ); - - const double totalConnectionFactor = RigTransmissibilityEquations::totalConnectionFactor( transx, transy, transz ); - - TransmissibilityData trData; - trData.setData( effectiveH, effectiveK, totalConnectionFactor, totalKh ); - return trData; -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -double RicWellPathExportCompletionDataFeatureImpl::calculateDFactor( RimEclipseCase* eclipseCase, - double effectiveH, - size_t globalCellIndex, - const RimNonDarcyPerforationParameters* nonDarcyParameters, - const double effectivePermeability ) -{ - using EQ = RigPerforationTransmissibilityEquations; - - if ( !eclipseCase || !eclipseCase->eclipseCaseData() ) - { - return std::numeric_limits::infinity(); - } - - RigEclipseCaseData* eclipseCaseData = eclipseCase->eclipseCaseData(); - - double porosity = 0.0; - { - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PORO" ) ); - cvf::ref poroAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PORO" ) ); - - if ( poroAccessObject.notNull() ) - { - porosity = poroAccessObject->cellScalar( globalCellIndex ); - } - } - - const double betaFactor = EQ::betaFactor( nonDarcyParameters->inertialCoefficientBeta0(), - effectivePermeability, - nonDarcyParameters->permeabilityScalingFactor(), - porosity, - nonDarcyParameters->porosityScalingFactor() ); - - const double alpha = RiaDefines::nonDarcyFlowAlpha( eclipseCaseData->unitsType() ); - - return EQ::dFactor( alpha, - betaFactor, - effectivePermeability, - effectiveH, - nonDarcyParameters->wellRadius(), - nonDarcyParameters->relativeGasDensity(), - nonDarcyParameters->gasViscosity() ); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -double RicWellPathExportCompletionDataFeatureImpl::calculateTransmissibilityAsEclipseDoes( RimEclipseCase* eclipseCase, - double skinFactor, - double wellRadius, - size_t globalCellIndex, - RigCompletionData::CellDirection direction ) -{ - RigEclipseCaseData* eclipseCaseData = eclipseCase->eclipseCaseData(); - - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DX" ) ); - cvf::ref dxAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DX" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DY" ) ); - cvf::ref dyAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DY" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "DZ" ) ); - cvf::ref dzAccessObject = RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "DZ" ) ); - - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PERMX" ) ); - cvf::ref permxAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PERMX" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PERMY" ) ); - cvf::ref permyAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PERMY" ) ); - eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "PERMZ" ) ); - cvf::ref permzAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "PERMZ" ) ); - - double ntg = 1.0; - if ( eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( RigEclipseResultAddress( "NTG" ) ) ) - { - cvf::ref ntgAccessObject = - RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, - 0, - RiaDefines::PorosityModelType::MATRIX_MODEL, - 0, - RigEclipseResultAddress( "NTG" ) ); - ntg = ntgAccessObject->cellScalarGlobIdx( globalCellIndex ); - } - - double dx = dxAccessObject->cellScalarGlobIdx( globalCellIndex ); - double dy = dyAccessObject->cellScalarGlobIdx( globalCellIndex ); - double dz = dzAccessObject->cellScalarGlobIdx( globalCellIndex ); - double permx = permxAccessObject->cellScalarGlobIdx( globalCellIndex ); - double permy = permyAccessObject->cellScalarGlobIdx( globalCellIndex ); - double permz = permzAccessObject->cellScalarGlobIdx( globalCellIndex ); - - RiaDefines::EclipseUnitSystem units = eclipseCaseData->unitsType(); - double darcy = RiaEclipseUnitTools::darcysConstant( units ); - - double trans = cvf::UNDEFINED_DOUBLE; - if ( direction == RigCompletionData::CellDirection::DIR_I ) - { - trans = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( dx, permy, permz, dy, dz, wellRadius, skinFactor, darcy ); - } - else if ( direction == RigCompletionData::CellDirection::DIR_J ) - { - trans = RigTransmissibilityEquations::wellBoreTransmissibilityComponent( dy, permx, permz, dx, dz, wellRadius, skinFactor, darcy ); - } - else if ( direction == RigCompletionData::CellDirection::DIR_K ) - { - trans = - RigTransmissibilityEquations::wellBoreTransmissibilityComponent( dz * ntg, permy, permx, dy, dx, wellRadius, skinFactor, darcy ); - } - - return trans; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h index d501010f8a..7722ca4dae 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h @@ -54,70 +54,12 @@ class SubSegmentIntersectionInfo; //================================================================================================== using QFilePtr = std::shared_ptr; -class TransmissibilityData -{ -public: - TransmissibilityData() - : m_isValid( false ) - , m_effectiveH( 0.0 ) - , m_effectiveK( 0.0 ) - , m_connectionFactor( 0.0 ) - , m_kh( 0.0 ) - { - } - - bool isValid() const { return m_isValid; } - - void setData( double effectiveH, double effectiveK, double connectionFactor, double kh ) - { - m_isValid = true; - - m_effectiveH = effectiveH; - m_effectiveK = effectiveK; - m_connectionFactor = connectionFactor; - m_kh = kh; - } - - double effectiveH() const { return m_effectiveH; } - - double effectiveK() const { return m_effectiveK; } - double connectionFactor() const { return m_connectionFactor; } - double kh() const { return m_kh; } - -private: - bool m_isValid; - double m_effectiveH; - double m_effectiveK; - double m_connectionFactor; - double m_kh; -}; - //================================================================================================== /// //================================================================================================== class RicWellPathExportCompletionDataFeatureImpl { public: - static RigCompletionData::CellDirection - calculateCellMainDirection( RimEclipseCase* eclipseCase, size_t globalCellIndex, const cvf::Vec3d& lengthsInCell ); - - static TransmissibilityData - calculateTransmissibilityData( RimEclipseCase* eclipseCase, - const RimWellPath* wellPath, - const cvf::Vec3d& internalCellLengths, - double skinFactor, - double wellRadius, - size_t globalCellIndex, - bool useLateralNTG, - size_t volumeScaleConstant = 1, - RigCompletionData::CellDirection directionForVolumeScaling = RigCompletionData::CellDirection::DIR_I ); - - static double calculateDFactor( RimEclipseCase* eclipseCase, - double effectiveH, - size_t globalCellIndex, - const RimNonDarcyPerforationParameters* nonDarcyParameters, - const double effectivePermeability ); - static void exportCompletions( const std::vector& wellPaths, const RicExportCompletionDataSettingsUi& exportSettings ); static std::vector computeStaticCompletionsForWellPath( RimWellPath* wellPath, RimEclipseCase* eclipseCase ); @@ -140,12 +82,6 @@ class RicWellPathExportCompletionDataFeatureImpl const RicExportCompletionDataSettingsUi& settings, const std::optional& exportDate = std::nullopt ); - static double calculateTransmissibilityAsEclipseDoes( RimEclipseCase* eclipseCase, - double skinFactor, - double wellRadius, - size_t globalCellIndex, - RigCompletionData::CellDirection direction ); - static RigCompletionData combineEclipseCellCompletions( const std::vector& completions, const RicExportCompletionDataSettingsUi& settings ); diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp index 264b1751c4..0c25664312 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp @@ -455,6 +455,13 @@ void RicWellPathExportMswTableData::appendFishbonesMswExportInfo( const RimEclip size_t i, j, k; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); + + // Shift K to fracture section for dual porosity models + if ( mainGrid->isDualPorosity() ) + { + k += mainGrid->cellCountK(); + } + caf::VecIjk0 localIJK( i, j, k ); auto mswIntersect = std::make_shared( gridName, @@ -1346,6 +1353,13 @@ void RicWellPathExportMswTableData::assignFishbonesLateralIntersections( const R size_t i = 0u, j = 0u, k = 0u; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); + + // For dual porosity models, shift K to the fracture section so exported completion data references the correct K-layer. + if ( grid->isDualPorosity() ) + { + k += grid->cellCountK(); + } + auto subSegment = std::make_unique( "Sub segment", previousExitMD, cellIntInfo.endMD, diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.cpp b/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.cpp index d20c97bddf..f5a0933a77 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.cpp @@ -455,6 +455,14 @@ std::vector RicExportLgrFeature::buildLgrsForWellPaths( std::vectormainGrid(); mainGrid && mainGrid->isDualPorosity() ) + { + kLayerOffset = mainGrid->cellCountK(); + } + int firstLgrId = firstAvailableLgrId( eclipseCase->mainGrid() ); if ( splitType == Lgr::LGR_PER_CELL ) @@ -503,7 +511,7 @@ std::vector RicExportLgrFeature::buildLgrsForWellPaths( std::vectorname(), intersectingCells, refinement ) ); + lgrs.push_back( buildLgr( lgrId, lgrName, wellPath->name(), intersectingCells, refinement, kLayerOffset ) ); } if ( isIntersectingOtherLgrs ) wellsIntersectingOtherLgrs->push_back( wellPath->name() ); @@ -524,11 +532,20 @@ std::vector RicExportLgrFeature::buildLgrsPerMainCell( int LgrNameFactory& lgrNameFactory ) { std::vector lgrs; - int lgrId = firstLgrId; + + // For dual porosity models, localCellIndexK() includes the fracture section offset. + // Subtract it here to get the original grid K for LGR bounding box computation. + size_t kLayerOffset = 0; + if ( auto mainGrid = eclipseCase->mainGrid(); mainGrid && mainGrid->isDualPorosity() ) + { + kLayerOffset = mainGrid->cellCountK(); + } + + int lgrId = firstLgrId; for ( const auto& intersectionCell : intersectingCells ) { auto lgrName = lgrNameFactory.newName( "", lgrId ); - lgrs.push_back( buildLgr( lgrId++, lgrName, wellPath->name(), { intersectionCell }, refinement ) ); + lgrs.push_back( buildLgr( lgrId++, lgrName, wellPath->name(), { intersectionCell }, refinement, kLayerOffset ) ); } return lgrs; } @@ -545,6 +562,14 @@ std::vector { std::vector lgrs; + // For dual porosity models, localCellIndexK() includes the fracture section offset. + // Subtract it here to get the original grid K for LGR bounding box computation. + size_t kLayerOffset = 0; + if ( auto mainGrid = eclipseCase->mainGrid(); mainGrid && mainGrid->isDualPorosity() ) + { + kLayerOffset = mainGrid->cellCountK(); + } + std::vector> occupiedBbs; for ( const auto& complInfo : completionInfo ) @@ -559,7 +584,7 @@ std::vector for ( const auto& cell : complCells ) { auto candidateBb = maxBb; - candidateBb.addCell( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); + candidateBb.addCell( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() - kLayerOffset ); // Test bounding box bool intersectsExistingBb = false; @@ -606,12 +631,13 @@ LgrInfo RicExportLgrFeature::buildLgr( int const QString& lgrName, const QString& wellPathName, const std::vector& intersectingCells, - const cvf::Vec3st& refinement ) + const cvf::Vec3st& refinement, + size_t kLayerOffset ) { IjkBoundingBox boundingBox; for ( const auto& cell : intersectingCells ) { - boundingBox.addCell( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); + boundingBox.addCell( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() - kLayerOffset ); } return buildLgr( lgrId, lgrName, wellPathName, boundingBox, refinement ); diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.h b/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.h index c185e85401..37c75c3c63 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.h +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportLgrFeature.h @@ -211,7 +211,8 @@ class RicExportLgrFeature : public caf::CmdFeature const QString& lgrName, const QString& wellPathName, const std::vector& intersectingCells, - const cvf::Vec3st& refinement ); + const cvf::Vec3st& refinement, + size_t kLayerOffset = 0 ); static LgrInfo buildLgr( int lgrId, const QString& lgrName, diff --git a/ApplicationLibCode/FileInterface/RifEclipseSummaryAddressDefines.h b/ApplicationLibCode/FileInterface/RifEclipseSummaryAddressDefines.h index 03543c3dcc..4e6c6815ab 100644 --- a/ApplicationLibCode/FileInterface/RifEclipseSummaryAddressDefines.h +++ b/ApplicationLibCode/FileInterface/RifEclipseSummaryAddressDefines.h @@ -26,7 +26,6 @@ //================================================================================================== namespace RifEclipseSummaryAddressDefines { -// Based on list in ecl_smspec.c and list of types taken from Eclipse Reference Manual ecl_rm_2011.1.pdf enum class SummaryCategory { SUMMARY_INVALID, diff --git a/ApplicationLibCode/ProjectDataModel/Rim3dOverlayInfoConfig.cpp b/ApplicationLibCode/ProjectDataModel/Rim3dOverlayInfoConfig.cpp index 8edc0eb964..d25ff26f1d 100644 --- a/ApplicationLibCode/ProjectDataModel/Rim3dOverlayInfoConfig.cpp +++ b/ApplicationLibCode/ProjectDataModel/Rim3dOverlayInfoConfig.cpp @@ -541,7 +541,8 @@ QString Rim3dOverlayInfoConfig::resultInfoText( const RigHistogramData& histData infoText += QString( "Well Disk Property: %1
" ).arg( wellCollection->wellDiskPropertyUiText() ); } - if ( eclipseView->cellResult()->hasDualPorFractureResult() ) + if ( eclipseView->eclipseCase() && eclipseView->eclipseCase()->mainGrid() && + eclipseView->eclipseCase()->mainGrid()->isDualPorosity() ) { QString porosityModelText = caf::AppEnum::uiText( eclipseView->cellResult()->porosityModel() ); diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.cpp index 71a0aa30d2..4034d1c1fa 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.cpp @@ -27,6 +27,7 @@ #include "RigEclipseCaseData.h" #include "RigFlowDiagResults.h" #include "RigFormationNames.h" +#include "RigMainGrid.h" #include "RimCellEdgeColors.h" #include "RimEclipseCase.h" @@ -110,6 +111,8 @@ void RimEclipseCellColors::fieldChangedByUi( const caf::PdmFieldHandle* changedF } } + updateUiTreeName(); + if ( m_reservoirView ) m_reservoirView->scheduleCreateDisplayModelAndRedraw(); } @@ -210,6 +213,7 @@ void RimEclipseCellColors::initAfterRead() changeLegendConfig( resultVariable() ); + updateUiTreeName(); updateIconState(); } @@ -248,6 +252,15 @@ void RimEclipseCellColors::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeO uiTreeOrdering.skipRemainingChildren( true ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseCellColors::loadResult() +{ + RimEclipseResultDefinition::loadResult(); + updateUiTreeName(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -280,6 +293,21 @@ void RimEclipseCellColors::setAdditionalUiTreeObjects( const std::vectormainGrid() && m_eclipseCase->mainGrid()->isDualPorosity() ) + { + QString porosityModelText = caf::AppEnum::uiText( porosityModel() ); + name += " [" + porosityModelText + "]"; + } + uiCapability()->setUiName( name ); + uiCapability()->updateConnectedEditors(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.h b/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.h index 94ea00c1f4..d78fdb1110 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCellColors.h @@ -52,6 +52,7 @@ class RimEclipseCellColors : public RimEclipseResultDefinition RimRegularLegendConfig* legendConfig(); RimTernaryLegendConfig* ternaryLegendConfig(); + void loadResult() override; void setResultVariable( const QString& resultName ) override; void updateIconState(); @@ -78,6 +79,7 @@ class RimEclipseCellColors : public RimEclipseResultDefinition void changeLegendConfig( QString resultVarNameOfNewLegend ); void onLegendConfigChanged( const caf::SignalEmitter* emitter, RimLegendConfigChangeType changeType ); static RimRegularLegendConfig* createLegendForResult( int caseId, const QString& resultName, bool useDiscreteLevels, bool isCategoryResult ); + void updateUiTreeName(); caf::PdmChildArrayField m_legendConfigData; caf::PdmPtrField m_legendConfigPtrField; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.cpp index 0df052cabd..935538a5ed 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.cpp @@ -34,6 +34,7 @@ #include "RigFlowDiagResultAddress.h" #include "RigFlowDiagResults.h" #include "RigFormationNames.h" +#include "RigMainGrid.h" #include "ContourMap/RimContourMapProjection.h" #include "ContourMap/RimEclipseContourMapProjection.h" @@ -115,6 +116,8 @@ RimEclipseResultDefinition::RimEclipseResultDefinition( caf::PdmUiItemInfo::Labe CAF_PDM_InitField( &m_divideByCellFaceArea, "DivideByCellFaceArea", false, "Divide By Area" ); + CAF_PDM_InitField( &m_showDualPorosityLabel, "ShowDualPorosityLabel", true, "Show Dual Porosity Label" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_selectedInjectorTracers, "SelectedInjectorTracers", "Injector Tracers" ); m_selectedInjectorTracers.uiCapability()->setUiHidden( true ); @@ -1477,9 +1480,10 @@ void RimEclipseResultDefinition::defineUiOrdering( QString uiConfigName, caf::Pd { uiOrdering.add( &m_resultTypeUiField ); - if ( hasDualPorFractureResult() ) + if ( m_eclipseCase && m_eclipseCase->mainGrid() && m_eclipseCase->mainGrid()->isDualPorosity() ) { uiOrdering.add( &m_porosityModelUiField ); + uiOrdering.add( &m_showDualPorosityLabel ); } if ( m_resultTypeUiField() == RiaDefines::ResultCatType::FLOW_DIAGNOSTICS ) @@ -1577,6 +1581,14 @@ void RimEclipseResultDefinition::defineUiOrdering( QString uiConfigName, caf::Pd uiOrdering.skipRemainingFields( true ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimEclipseResultDefinition::showDualPorosityLabel() const +{ + return m_showDualPorosityLabel; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1611,19 +1623,6 @@ void RimEclipseResultDefinition::assignFlowSolutionFromCase() setFlowSolution( defaultFlowDiagSolution ); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -bool RimEclipseResultDefinition::hasDualPorFractureResult() -{ - if ( m_eclipseCase && m_eclipseCase->eclipseCaseData() ) - { - return m_eclipseCase->eclipseCaseData()->hasFractureResults(); - } - - return false; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1730,7 +1729,7 @@ void RimEclipseResultDefinition::updateLegendTitle( RimRegularLegendConfig* lege title += additionalResultTextShort(); } - if ( hasDualPorFractureResult() ) + if ( m_eclipseCase && m_eclipseCase->mainGrid() && m_eclipseCase->mainGrid()->isDualPorosity() ) { QString porosityModelText = caf::AppEnum::uiText( porosityModel() ); diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.h b/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.h index 2eb4bfea14..bf42930702 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseResultDefinition.h @@ -102,7 +102,7 @@ class RimEclipseResultDefinition : public RimCheckableObject int caseDiffIndex() const; QString additionalResultText() const; - void loadResult(); + virtual void loadResult(); RigEclipseResultAddress eclipseResultAddress() const; void setFromEclipseResultAddress( const RigEclipseResultAddress& resultAddress ); bool hasStaticResult() const; @@ -126,7 +126,7 @@ class RimEclipseResultDefinition : public RimCheckableObject void updateUiFieldsFromActiveResult(); - bool hasDualPorFractureResult(); + bool showDualPorosityLabel() const; static QList calcOptionsForVariableUiFieldStandard( RiaDefines::ResultCatType resultCatType, const RigCaseCellResultsData* results, @@ -198,6 +198,7 @@ class RimEclipseResultDefinition : public RimCheckableObject caf::PdmField m_timeLapseBaseTimestep; caf::PdmPtrField m_differenceCase; caf::PdmField m_divideByCellFaceArea; + caf::PdmField m_showDualPorosityLabel; private: void assignFlowSolutionFromCase(); diff --git a/ApplicationLibCode/ReservoirDataModel/Completions/RigCompletionDataGridCell.cpp b/ApplicationLibCode/ReservoirDataModel/Completions/RigCompletionDataGridCell.cpp index 747b1a40ff..cd94591afd 100644 --- a/ApplicationLibCode/ReservoirDataModel/Completions/RigCompletionDataGridCell.cpp +++ b/ApplicationLibCode/ReservoirDataModel/Completions/RigCompletionDataGridCell.cpp @@ -55,6 +55,13 @@ RigCompletionDataGridCell::RigCompletionDataGridCell( size_t globalCellIndex, co m_localCellIndexJ = j; m_localCellIndexK = k; + // For dual porosity models, the first N K-layers represent MATRIX and K-layers N+1 to 2N represent FRACTURE. + // Shift K to the fracture section so exported completion data references the correct K-layer. + if ( mainGrid->isDualPorosity() ) + { + m_localCellIndexK += mainGrid->cellCountK(); + } + if ( grid != mainGrid ) { m_lgrName = QString::fromStdString( grid->gridName() ); diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 6f0df61093..5755033043 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -124,6 +124,8 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RifSurfio-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifRmsWellPathReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaAngleUtils-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicTransmissibilityCalculator-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigCompletionDataGridCell-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/cafVecIjk-Test.cpp ) diff --git a/ApplicationLibCode/UnitTests/RicTransmissibilityCalculator-Test.cpp b/ApplicationLibCode/UnitTests/RicTransmissibilityCalculator-Test.cpp new file mode 100644 index 0000000000..1fee997a6b --- /dev/null +++ b/ApplicationLibCode/UnitTests/RicTransmissibilityCalculator-Test.cpp @@ -0,0 +1,221 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 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 "gtest/gtest.h" + +#include "CompletionExportCommands/RicTransmissibilityCalculator.h" + +#include "RifReaderMockModel.h" + +#include "RigActiveCellInfo.h" +#include "RigCaseCellResultsData.h" +#include "RigEclipseCaseData.h" +#include "RigEclipseResultAddress.h" +#include "RigEclipseResultInfo.h" +#include "RigMainGrid.h" + +#include "RimEclipseCase.h" +#include "RimEclipseResultCase.h" +#include "RimWellPath.h" + +//-------------------------------------------------------------------------------------------------- +/// Helper: Register a STATIC_NATIVE result with a uniform value for all cells +//-------------------------------------------------------------------------------------------------- +static void addStaticResult( RigCaseCellResultsData* cellResults, const QString& name, size_t cellCount, double value ) +{ + RigEclipseResultAddress address( RiaDefines::ResultCatType::STATIC_NATIVE, name ); + cellResults->createResultEntry( address, false ); + + auto timeStepInfo = RigEclipseTimeStepInfo( QDateTime(), 0, 0.0 ); + cellResults->setTimeStepInfos( address, { timeStepInfo } ); + + auto* timesteps = cellResults->modifiableCellScalarResultTimesteps( address ); + timesteps->resize( 1 ); + ( *timesteps )[0].assign( cellCount, value ); +} + +//-------------------------------------------------------------------------------------------------- +/// Helper: Create a RimEclipseCase with mock grid and populated result data +//-------------------------------------------------------------------------------------------------- +static RimEclipseCase* + createMockEclipseCase( double dxVal, double dyVal, double dzVal, double permxVal, double permyVal, double permzVal, double ntgVal ) +{ + auto* eclipseCase = new RimEclipseResultCase; + + cvf::ref caseData = new RigEclipseCaseData( eclipseCase ); + + { + cvf::ref mockReader = new RifReaderMockModel; + mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, 0 ), cvf::Vec3d( 100, 100, 100 ) ); + mockReader->setCellCounts( cvf::Vec3st( 2, 2, 2 ) ); + mockReader->enableWellData( false ); + mockReader->open( "", caseData.p() ); + caseData->mainGrid()->computeCachedData(); + } + + eclipseCase->setReservoirData( caseData.p() ); + + size_t cellCount = caseData->mainGrid()->totalCellCount(); + RigCaseCellResultsData* cellResults = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + addStaticResult( cellResults, "DX", cellCount, dxVal ); + addStaticResult( cellResults, "DY", cellCount, dyVal ); + addStaticResult( cellResults, "DZ", cellCount, dzVal ); + addStaticResult( cellResults, "PERMX", cellCount, permxVal ); + addStaticResult( cellResults, "PERMY", cellCount, permyVal ); + addStaticResult( cellResults, "PERMZ", cellCount, permzVal ); + addStaticResult( cellResults, "NTG", cellCount, ntgVal ); + + return eclipseCase; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicTransmissibilityCalculator, CalculateCellMainDirection_DirectionI ) +{ + RimEclipseCase* eclipseCase = createMockEclipseCase( 10.0, 20.0, 30.0, 100.0, 100.0, 10.0, 1.0 ); + + // lengthsInCell = (8, 3, 2) → fractions: x=0.8, y=0.15, z=0.067 → DIR_I + cvf::Vec3d lengthsInCell( 8.0, 3.0, 2.0 ); + + auto direction = RicTransmissibilityCalculator::calculateCellMainDirection( eclipseCase, 0, lengthsInCell ); + + EXPECT_EQ( RigCompletionData::CellDirection::DIR_I, direction ); + + delete eclipseCase; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicTransmissibilityCalculator, CalculateCellMainDirection_DirectionK ) +{ + RimEclipseCase* eclipseCase = createMockEclipseCase( 10.0, 20.0, 30.0, 100.0, 100.0, 10.0, 1.0 ); + + // lengthsInCell = (1, 1, 25) → fractions: x=0.1, y=0.05, z=0.833 → DIR_K + cvf::Vec3d lengthsInCell( 1.0, 1.0, 25.0 ); + + auto direction = RicTransmissibilityCalculator::calculateCellMainDirection( eclipseCase, 0, lengthsInCell ); + + EXPECT_EQ( RigCompletionData::CellDirection::DIR_K, direction ); + + delete eclipseCase; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicTransmissibilityCalculator, CalculateTransmissibilityData_Valid ) +{ + RimEclipseCase* eclipseCase = createMockEclipseCase( 100.0, 100.0, 10.0, 100.0, 100.0, 10.0, 1.0 ); + + RimWellPath wellPath; + wellPath.setUnitSystem( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + + cvf::Vec3d internalCellLengths( 50.0, 50.0, 5.0 ); + double skinFactor = 0.0; + double wellRadius = 0.1; + + auto result = + RicTransmissibilityCalculator::calculateTransmissibilityData( eclipseCase, &wellPath, internalCellLengths, skinFactor, wellRadius, 0, false ); + + EXPECT_TRUE( result.isValid() ); + EXPECT_GT( result.connectionFactor(), 0.0 ); + EXPECT_GT( result.kh(), 0.0 ); + EXPECT_GT( result.effectiveH(), 0.0 ); + EXPECT_GT( result.effectiveK(), 0.0 ); + + delete eclipseCase; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicTransmissibilityCalculator, CalculateTransmissibilityAsEclipseDoes_DirectionK ) +{ + RimEclipseCase* eclipseCase = createMockEclipseCase( 100.0, 100.0, 10.0, 100.0, 100.0, 10.0, 1.0 ); + + double skinFactor = 0.0; + double wellRadius = 0.1; + + double trans = RicTransmissibilityCalculator::calculateTransmissibilityAsEclipseDoes( eclipseCase, + skinFactor, + wellRadius, + 0, + RigCompletionData::CellDirection::DIR_K ); + + EXPECT_GT( trans, 0.0 ); + + delete eclipseCase; +} + +//-------------------------------------------------------------------------------------------------- +/// Helper: Set up fracture active cell info and enable dual porosity +//-------------------------------------------------------------------------------------------------- +static void enableDualPorosity( RigEclipseCaseData* caseData ) +{ + size_t cellCount = caseData->mainGrid()->totalCellCount(); + + RigActiveCellInfo* fractureActiveCellInfo = caseData->activeCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL ); + fractureActiveCellInfo->setReservoirCellCount( cellCount ); + for ( size_t i = 0; i < cellCount; i++ ) + { + fractureActiveCellInfo->setCellResultIndex( i, i ); + } + fractureActiveCellInfo->setGridCount( 1 ); + fractureActiveCellInfo->setGridActiveCellCounts( 0, cellCount ); + fractureActiveCellInfo->computeDerivedData(); + + caseData->mainGrid()->setDualPorosity( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicTransmissibilityCalculator, CalculateCellMainDirection_DualPorosity_UsesFractureProperties ) +{ + // Matrix: DX=10, DY=20, DZ=30. Fracture: DX=100, DY=20, DZ=30. + // lengthsInCell = (8, 3, 2) + // Matrix fracs: x=8/10=0.8, y=3/20=0.15, z=2/30=0.067 → DIR_I + // Fracture fracs: x=8/100=0.08, y=3/20=0.15, z=2/30=0.067 → DIR_J + RimEclipseCase* eclipseCase = createMockEclipseCase( 10.0, 20.0, 30.0, 100.0, 100.0, 10.0, 1.0 ); + + RigEclipseCaseData* caseData = eclipseCase->eclipseCaseData(); + size_t cellCount = caseData->mainGrid()->totalCellCount(); + + enableDualPorosity( caseData ); + + // Populate fracture results with different DX + RigCaseCellResultsData* fractureResults = caseData->results( RiaDefines::PorosityModelType::FRACTURE_MODEL ); + addStaticResult( fractureResults, "DX", cellCount, 100.0 ); + addStaticResult( fractureResults, "DY", cellCount, 20.0 ); + addStaticResult( fractureResults, "DZ", cellCount, 30.0 ); + addStaticResult( fractureResults, "PERMX", cellCount, 100.0 ); + addStaticResult( fractureResults, "PERMY", cellCount, 100.0 ); + addStaticResult( fractureResults, "PERMZ", cellCount, 10.0 ); + + cvf::Vec3d lengthsInCell( 8.0, 3.0, 2.0 ); + + auto direction = RicTransmissibilityCalculator::calculateCellMainDirection( eclipseCase, 0, lengthsInCell ); + + // With matrix properties this would be DIR_I, but with fracture properties it should be DIR_J + EXPECT_EQ( RigCompletionData::CellDirection::DIR_J, direction ); + + delete eclipseCase; +} diff --git a/ApplicationLibCode/UnitTests/RigCompletionDataGridCell-Test.cpp b/ApplicationLibCode/UnitTests/RigCompletionDataGridCell-Test.cpp new file mode 100644 index 0000000000..a2ab3a7a8e --- /dev/null +++ b/ApplicationLibCode/UnitTests/RigCompletionDataGridCell-Test.cpp @@ -0,0 +1,109 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 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 "gtest/gtest.h" + +#include "RigCompletionDataGridCell.h" + +#include "RifReaderMockModel.h" + +#include "RigEclipseCaseData.h" +#include "RigMainGrid.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RigCompletionDataGridCellTest, LocalCellIndexK_StandardModel ) +{ + cvf::ref caseData = new RigEclipseCaseData( nullptr ); + + cvf::ref mockReader = new RifReaderMockModel; + mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, 0 ), cvf::Vec3d( 100, 100, 100 ) ); + mockReader->setCellCounts( cvf::Vec3st( 2, 2, 3 ) ); + mockReader->enableWellData( false ); + mockReader->open( "", caseData.p() ); + caseData->mainGrid()->computeCachedData(); + + RigMainGrid* mainGrid = caseData->mainGrid(); + ASSERT_FALSE( mainGrid->isDualPorosity() ); + ASSERT_EQ( 3u, mainGrid->cellCountK() ); + + // Cell at (0, 0, 0) - first K-layer + RigCompletionDataGridCell cell0( 0, mainGrid ); + EXPECT_EQ( 0u, cell0.localCellIndexK() ); + + // Cell at (0, 0, 2) - last K-layer (global index = 2 * 2*2 = 8) + RigCompletionDataGridCell cell2( 8, mainGrid ); + EXPECT_EQ( 2u, cell2.localCellIndexK() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RigCompletionDataGridCellTest, LocalCellIndexK_DualPorosityModel ) +{ + cvf::ref caseData = new RigEclipseCaseData( nullptr ); + + cvf::ref mockReader = new RifReaderMockModel; + mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, 0 ), cvf::Vec3d( 100, 100, 100 ) ); + mockReader->setCellCounts( cvf::Vec3st( 2, 2, 3 ) ); + mockReader->enableWellData( false ); + mockReader->open( "", caseData.p() ); + caseData->mainGrid()->computeCachedData(); + + RigMainGrid* mainGrid = caseData->mainGrid(); + mainGrid->setDualPorosity( true ); + ASSERT_TRUE( mainGrid->isDualPorosity() ); + + size_t cellCountK = mainGrid->cellCountK(); + ASSERT_EQ( 3u, cellCountK ); + + // Cell at (0, 0, 0) - first K-layer: should be offset by cellCountK + RigCompletionDataGridCell cell0( 0, mainGrid ); + EXPECT_EQ( 0u + cellCountK, cell0.localCellIndexK() ); + + // Cell at (0, 0, 2) - last K-layer: should be offset by cellCountK + RigCompletionDataGridCell cell2( 8, mainGrid ); + EXPECT_EQ( 2u + cellCountK, cell2.localCellIndexK() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RigCompletionDataGridCellTest, OneBasedString_DualPorosityModel ) +{ + cvf::ref caseData = new RigEclipseCaseData( nullptr ); + + cvf::ref mockReader = new RifReaderMockModel; + mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, 0 ), cvf::Vec3d( 100, 100, 100 ) ); + mockReader->setCellCounts( cvf::Vec3st( 2, 2, 3 ) ); + mockReader->enableWellData( false ); + mockReader->open( "", caseData.p() ); + caseData->mainGrid()->computeCachedData(); + + RigMainGrid* mainGrid = caseData->mainGrid(); + + // Without dual porosity: cell (0,0,0) should give "[1, 1, 1]" + RigCompletionDataGridCell cellStandard( 0, mainGrid ); + EXPECT_EQ( QString( "[1, 1, 1]" ), cellStandard.oneBasedLocalCellIndexString() ); + + // With dual porosity: cell (0,0,0) should give "[1, 1, 4]" (K offset by 3) + mainGrid->setDualPorosity( true ); + RigCompletionDataGridCell cellDual( 0, mainGrid ); + EXPECT_EQ( QString( "[1, 1, 4]" ), cellDual.oneBasedLocalCellIndexString() ); +} diff --git a/ApplicationLibCode/UserInterface/RiuPvtPlotUpdater.cpp b/ApplicationLibCode/UserInterface/RiuPvtPlotUpdater.cpp index 108d8e553f..acef4a7dc4 100644 --- a/ApplicationLibCode/UserInterface/RiuPvtPlotUpdater.cpp +++ b/ApplicationLibCode/UserInterface/RiuPvtPlotUpdater.cpp @@ -149,7 +149,8 @@ bool RiuPvtPlotUpdater::queryDataAndUpdatePlot( const RimEclipseResultDefinition &viscosityDynProps.mu_o, &viscosityDynProps.mu_g ); - QString cellRefText = RiuRelativePermeabilityPlotUpdater::constructCellReferenceText( eclipseCaseData, gridIndex, gridLocalCellIndex ); + QString cellRefText = + RiuRelativePermeabilityPlotUpdater::constructCellReferenceText( eclipseCaseData, gridIndex, gridLocalCellIndex, porosityModel ); cellRefText += QString( ", PVTNUM: %1" ).arg( cellPvtNum ); m_targetPlotPanel->setPlotData( eclipseCaseData->unitsType(), diff --git a/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.cpp b/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.cpp index 2bafb32f93..83d5836a94 100644 --- a/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.cpp +++ b/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.cpp @@ -171,6 +171,8 @@ bool RiuRelativePermeabilityPlotUpdater::queryDataAndUpdatePlot( const RimEclips // for the matrix model is used. It will require some changes in the flow diagnostics module to be // able support fracture model plotting. + RiaLogging::warning( "Relative permeability curves for fracture cells is not supported." ); + return false; } @@ -236,7 +238,7 @@ bool RiuRelativePermeabilityPlotUpdater::queryDataAndUpdatePlot( const RimEclips std::vector relPermCurveArr = eclipseResultCase->flowDiagSolverInterface()->calculateRelPermCurves( gridName, localActiveCellIndex ); - QString cellRefText = constructCellReferenceText( eclipseCaseData, gridIndex, gridLocalCellIndex ); + QString cellRefText = constructCellReferenceText( eclipseCaseData, gridIndex, gridLocalCellIndex, eclipseResDef->porosityModel() ); if ( satnumAccessor.notNull() ) { @@ -267,9 +269,10 @@ bool RiuRelativePermeabilityPlotUpdater::queryDataAndUpdatePlot( const RimEclips //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -QString RiuRelativePermeabilityPlotUpdater::constructCellReferenceText( const RigEclipseCaseData* eclipseCaseData, - size_t gridIndex, - size_t gridLocalCellIndex ) +QString RiuRelativePermeabilityPlotUpdater::constructCellReferenceText( const RigEclipseCaseData* eclipseCaseData, + size_t gridIndex, + size_t gridLocalCellIndex, + RiaDefines::PorosityModelType porosityModel ) { const size_t gridCount = eclipseCaseData ? eclipseCaseData->gridCount() : 0; const RigGridBase* grid = gridIndex < gridCount ? eclipseCaseData->grid( gridIndex ) : nullptr; @@ -280,6 +283,15 @@ QString RiuRelativePermeabilityPlotUpdater::constructCellReferenceText( const Ri size_t k = 0; if ( grid->ijkFromCellIndex( gridLocalCellIndex, &i, &j, &k ) ) { + // For dual porosity models, shift K to the fracture section + if ( porosityModel == RiaDefines::PorosityModelType::FRACTURE_MODEL ) + { + if ( auto mainGrid = eclipseCaseData->mainGrid() ) + { + k += mainGrid->cellCountK(); + } + } + // Adjust to 1-based Eclipse indexing i++; j++; diff --git a/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.h b/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.h index a2701d6006..b321069898 100644 --- a/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.h +++ b/ApplicationLibCode/UserInterface/RiuRelativePermeabilityPlotUpdater.h @@ -18,6 +18,7 @@ #pragma once +#include "RiaPorosityModel.h" #include "RiuPlotUpdater.h" class RiuRelativePermeabilityPlotPanel; @@ -34,7 +35,10 @@ class RiuRelativePermeabilityPlotUpdater : public RiuPlotUpdater public: RiuRelativePermeabilityPlotUpdater( RiuRelativePermeabilityPlotPanel* targetPlotPanel ); - static QString constructCellReferenceText( const RigEclipseCaseData* eclipseCaseData, size_t gridIndex, size_t gridLocalCellIndex ); + static QString constructCellReferenceText( const RigEclipseCaseData* eclipseCaseData, + size_t gridIndex, + size_t gridLocalCellIndex, + RiaDefines::PorosityModelType porosityModel ); protected: void clearPlot() override; diff --git a/ApplicationLibCode/UserInterface/RiuResultTextBuilder.cpp b/ApplicationLibCode/UserInterface/RiuResultTextBuilder.cpp index f05e102a75..2fe8a427e9 100644 --- a/ApplicationLibCode/UserInterface/RiuResultTextBuilder.cpp +++ b/ApplicationLibCode/UserInterface/RiuResultTextBuilder.cpp @@ -214,6 +214,12 @@ QString RiuResultTextBuilder::geometrySelectionText( const QString& itemSeparato const RigGridBase* grid = eclipseCase->grid( m_gridIndex ); if ( grid->ijkFromCellIndex( m_cellIndex, &i, &j, &k ) ) { + if ( eclipseCase->mainGrid()->isDualPorosity() && + m_eclResDef->porosityModel() == RiaDefines::PorosityModelType::FRACTURE_MODEL ) + { + k += eclipseCase->mainGrid()->cellCountK(); + } + // Adjust to 1-based Eclipse indexing i++; j++;