From a6c2b1b911d40d3b4b3a35be83795d9ccb778a96 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Sat, 24 Oct 2026 13:51:33 +0200 Subject: [PATCH] #15021: Add unit tests for completion export and non-Darcy D-factor --- ApplicationLibCode/UnitTests/CMakeLists.txt | 4 + .../RicCompletionDataExportTools-Test.cpp | 227 +++++++++++++++ ...thExportCompletionDataFeatureImpl-Test.cpp | 263 +++++++++++++++++- ...forationTransmissibilityEquations-Test.cpp | 70 +++++ .../RimFractureTemplateNonDarcy-Test.cpp | 205 ++++++++++++++ .../RimStimPlanFractureTemplate-Test.cpp | 151 ++++++++++ .../uniform_width_beta.xml | 52 ++++ 7 files changed, 970 insertions(+), 2 deletions(-) create mode 100644 ApplicationLibCode/UnitTests/RicCompletionDataExportTools-Test.cpp create mode 100644 ApplicationLibCode/UnitTests/RigPerforationTransmissibilityEquations-Test.cpp create mode 100644 ApplicationLibCode/UnitTests/RimFractureTemplateNonDarcy-Test.cpp create mode 100644 ApplicationLibCode/UnitTests/RimStimPlanFractureTemplate-Test.cpp create mode 100644 ApplicationLibCode/UnitTests/TestData/RimStimPlanFractureTemplate/uniform_width_beta.xml diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 71a3f17389..4f7549d698 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -77,6 +77,9 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RifElementPropertyTableReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RimRelocatePath-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigTransmissibilityCondenser-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigPerforationTransmissibilityEquations-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimFractureTemplateNonDarcy-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimStimPlanFractureTemplate-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaEclipseUnitTools-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaTextFileCompare-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifCaseRealizationParametersReader-Test.cpp @@ -220,6 +223,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RigAllGridCellsResultAccessor-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RicScheduleDataGenerator-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RicWellPathExportCompletionDataFeatureImpl-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicCompletionDataExportTools-Test.cpp ) if(RESINSIGHT_ENABLE_GRPC) diff --git a/ApplicationLibCode/UnitTests/RicCompletionDataExportTools-Test.cpp b/ApplicationLibCode/UnitTests/RicCompletionDataExportTools-Test.cpp new file mode 100644 index 0000000000..5469c5bfde --- /dev/null +++ b/ApplicationLibCode/UnitTests/RicCompletionDataExportTools-Test.cpp @@ -0,0 +1,227 @@ +#include "gtest/gtest.h" + +#include "CompletionExportCommands/RicCompletionDataExportTools.h" +#include "CompletionExportCommands/RicExportCompletionDataSettingsUi.h" + +#include "RifTextDataTableFormatter.h" + +#include "RigCompletionData.h" + +#include +#include +#include + +#include + +namespace +{ +RigCompletionData + makePerforation( double transmissibility, double diameter, double skin, double dFactor, double startMD = 100.0, double endMD = 110.0 ) +{ + RigCompletionData completion( "W", RigCompletionDataGridCell(), startMD ); + completion.setTransAndWPImultBackgroundDataFromPerforation( transmissibility, + skin, + diameter, + dFactor, + RigCompletionData::defaultValue(), + RigCompletionData::CellDirection::DIR_K ); + completion.setDepthRange( startMD, endMD ); + return completion; +} + +// Owns the PDM object locally, so it is not alive after the test environment is torn down +struct ExplicitTransSettings +{ + ExplicitTransSettings() { m_settings.compdatExport = RicExportCompletionDataSettingsUi::CompdatExport::TRANSMISSIBILITIES; } + + operator const RicExportCompletionDataSettingsUi&() const { return m_settings; } + + RicExportCompletionDataSettingsUi m_settings; +}; + +ExplicitTransSettings explicitTransSettings() +{ + return {}; +} + +QString exportTable( void ( *exporter )( RifTextDataTableFormatter&, const QString&, const std::vector& ), + const std::vector& completions ) +{ + QString text; + QTextStream stream( &text ); + RifTextDataTableFormatter formatter( stream ); + exporter( formatter, QString(), completions ); + return text; +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CombineSumsTransmissibilityAndWeightsValues ) +{ + const auto combined = RicCompletionDataExportTools::combineEclipseCellCompletions( { makePerforation( 1.0, 0.2, 1.0, 2.0 ), + makePerforation( 3.0, 0.4, 5.0, 4.0 ) }, + explicitTransSettings() ); + + EXPECT_DOUBLE_EQ( 4.0, combined.transmissibility() ); + EXPECT_DOUBLE_EQ( ( 0.2 * 1.0 + 0.4 * 3.0 ) / 4.0, combined.diameter() ); + EXPECT_DOUBLE_EQ( ( 1.0 * 1.0 + 5.0 * 3.0 ) / 4.0, combined.skinFactor() ); + EXPECT_EQ( RigCompletionData::CompletionType::PERFORATION, combined.completionType() ); +} + +//-------------------------------------------------------------------------------------------------- +/// The combined D-factor is the transmissibility weighted mean divided by the number of completions +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CombineDFactorIsWeightedMeanDividedByCount ) +{ + const auto combined = RicCompletionDataExportTools::combineEclipseCellCompletions( { makePerforation( 1.0, 0.2, 0.0, 2.0 ), + makePerforation( 3.0, 0.2, 0.0, 4.0 ) }, + explicitTransSettings() ); + + EXPECT_DOUBLE_EQ( ( 2.0 * 1.0 + 4.0 * 3.0 ) / 4.0 / 2.0, combined.dFactor() ); + + const auto single = + RicCompletionDataExportTools::combineEclipseCellCompletions( { makePerforation( 2.0, 0.2, 0.0, 3.0 ) }, explicitTransSettings() ); + EXPECT_DOUBLE_EQ( 3.0, single.dFactor() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CombineMergesMeasuredDepthRange ) +{ + const auto combined = RicCompletionDataExportTools::combineEclipseCellCompletions( { makePerforation( 1.0, 0.2, 0.0, 1.0, 100.0, 110.0 ), + makePerforation( 1.0, 0.2, 0.0, 1.0, 105.0, 130.0 ) }, + explicitTransSettings() ); + + ASSERT_TRUE( combined.startMD().has_value() ); + ASSERT_TRUE( combined.endMD().has_value() ); + EXPECT_DOUBLE_EQ( 100.0, combined.startMD().value() ); + EXPECT_DOUBLE_EQ( 130.0, combined.endMD().value() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CombineIgnoresNegativeTransmissibility ) +{ + const auto combined = RicCompletionDataExportTools::combineEclipseCellCompletions( { makePerforation( 2.0, 0.2, 0.0, 1.0 ), + makePerforation( -1.0, 0.2, 0.0, 1.0 ) }, + explicitTransSettings() ); + + EXPECT_DOUBLE_EQ( 2.0, combined.transmissibility() ); + // Skipped completions must not count in the divisor of the combined D-factor + EXPECT_DOUBLE_EQ( 1.0, combined.dFactor() ); + ASSERT_FALSE( combined.metadata().empty() ); + EXPECT_EQ( "ERROR", combined.metadata().front().name ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CombineDifferentWellsGivesUnexportedCompletion ) +{ + RigCompletionData other = makePerforation( 1.0, 0.2, 0.0, 1.0 ); + RigCompletionData otherWell( "OTHER", RigCompletionDataGridCell(), 100.0 ); + otherWell.setTransAndWPImultBackgroundDataFromPerforation( 1.0, 0.0, 0.2, 1.0, 1.0, RigCompletionData::CellDirection::DIR_K ); + + const auto combined = RicCompletionDataExportTools::combineEclipseCellCompletions( { other, otherWell }, explicitTransSettings() ); + + EXPECT_TRUE( RigCompletionData::isDefaultValue( combined.transmissibility() ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CombineIgnoresInfiniteTransmissibility ) +{ + const auto combined = RicCompletionDataExportTools::combineEclipseCellCompletions( { makePerforation( HUGE_VAL, 0.2, 0.0, 1.0 ) }, + explicitTransSettings() ); + + EXPECT_DOUBLE_EQ( 0.0, combined.transmissibility() ); + ASSERT_FALSE( combined.metadata().empty() ); + EXPECT_EQ( "ERROR", combined.metadata().front().name ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, MainGridCompletionsKeepsCellsWithoutLgr ) +{ + std::vector completions = { makePerforation( 1.0, 0.2, 0.0, 1.0 ), makePerforation( 2.0, 0.2, 0.0, 1.0 ) }; + + EXPECT_EQ( 2u, RicCompletionDataExportTools::mainGridCompletions( completions ).size() ); + EXPECT_TRUE( RicCompletionDataExportTools::subGridsCompletions( completions ).empty() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, AppendCompletionDataGroupsByGlobalCellIndex ) +{ + std::map> grouped; + + RicCompletionDataExportTools::appendCompletionData( &grouped, + { makePerforation( 1.0, 0.2, 0.0, 1.0 ), makePerforation( 2.0, 0.2, 0.0, 1.0 ) } ); + RicCompletionDataExportTools::appendCompletionData( &grouped, { makePerforation( 3.0, 0.2, 0.0, 1.0 ) } ); + + ASSERT_EQ( 1u, grouped.size() ); + EXPECT_EQ( 3u, grouped.begin()->second.size() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CompdatUsesDefaultMarkerWithoutDFactor ) +{ + const QString text = exportTable( RicCompletionDataExportTools::exportCompdatTableUsingFormatter, + { makePerforation( 1.5, 0.2, 0.0, RigCompletionData::defaultValue() ) } ); + + EXPECT_TRUE( text.contains( "COMPDAT" ) ) << text.toStdString(); + EXPECT_TRUE( text.contains( "'Z'" ) ) << text.toStdString(); + // The D-factor column is the last one before the direction and is defaulted + EXPECT_TRUE( text.contains( QRegularExpression( "1\\*\\s+'Z'" ) ) ) << text.toStdString(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CompdatWritesDFactorWhenSet ) +{ + auto completion = makePerforation( 1.5, 0.2, 0.0, 2.5 ); + completion.setPerConnectionDfactor(); + + const QString text = exportTable( RicCompletionDataExportTools::exportCompdatTableUsingFormatter, { completion } ); + + EXPECT_FALSE( text.contains( QRegularExpression( "1\\*\\s+'Z'" ) ) ) << text.toStdString(); + EXPECT_TRUE( text.contains( "-2.5" ) ) << text.toStdString(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, CompdatSkipsCompletionsWithoutTransmissibility ) +{ + const QString text = + exportTable( RicCompletionDataExportTools::exportCompdatTableUsingFormatter, { makePerforation( 0.0, 0.2, 0.0, 1.0 ) } ); + + EXPECT_FALSE( text.contains( "'Z'" ) ) << text.toStdString(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RicCompletionDataExportTools, ComplumpOnlyExportsCompletionsWithNumber ) +{ + auto withNumber = makePerforation( 1.5, 0.2, 0.0, 1.0 ); + withNumber.setCompletionNumber( 7 ); + auto withoutNumber = makePerforation( 1.5, 0.2, 0.0, 1.0 ); + + const QString text = exportTable( RicCompletionDataExportTools::exportComplumpTableUsingFormatter, { withoutNumber } ); + EXPECT_FALSE( text.contains( "7" ) ) << text.toStdString(); + + const QString textWithNumber = exportTable( RicCompletionDataExportTools::exportComplumpTableUsingFormatter, { withNumber } ); + EXPECT_TRUE( textWithNumber.contains( "COMPLUMP" ) ); + EXPECT_TRUE( textWithNumber.contains( QRegularExpression( "\\s7\\s" ) ) ) << textWithNumber.toStdString(); +} diff --git a/ApplicationLibCode/UnitTests/RicWellPathExportCompletionDataFeatureImpl-Test.cpp b/ApplicationLibCode/UnitTests/RicWellPathExportCompletionDataFeatureImpl-Test.cpp index cd9e288f72..db67fffbf4 100644 --- a/ApplicationLibCode/UnitTests/RicWellPathExportCompletionDataFeatureImpl-Test.cpp +++ b/ApplicationLibCode/UnitTests/RicWellPathExportCompletionDataFeatureImpl-Test.cpp @@ -20,13 +20,143 @@ #include "Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h" +#include "CompletionExportCommands/RicCompletionDataExportTools.h" +#include "CompletionExportCommands/RicTransmissibilityCalculator.h" +#include "RiaEclipseUnitTools.h" + +#include "RiaFractureDefines.h" +#include "RifReaderMockModel.h" + +#include "RigActiveCellInfo.h" +#include "RigCaseCellResultsData.h" #include "RigCompletionData.h" +#include "RigEclipseCaseData.h" +#include "RigEclipseResultAddress.h" +#include "RigEclipseResultInfo.h" +#include "RigMainGrid.h" +#include "RigPerforationTransmissibilityEquations.h" + +#include "Well/RigWellPath.h" + +#include "RimEclipseResultCase.h" +#include "RimNonDarcyPerforationParameters.h" +#include "RimOilField.h" +#include "RimPerforationCollection.h" +#include "RimPerforationInterval.h" +#include "RimProject.h" +#include "RimWellPath.h" +#include "RimWellPathCollection.h" + +#include "cafPdmField.h" #include +#include #include namespace { +void addStaticResult( RigCaseCellResultsData* cellResults, const QString& name, size_t cellCount, double value ) +{ + RigEclipseResultAddress address( RiaDefines::ResultCatType::STATIC_NATIVE, name ); + cellResults->createResultEntry( address, false ); + cellResults->setTimeStepInfos( address, { RigEclipseTimeStepInfo( QDateTime(), 0, 0.0 ) } ); + + auto* timesteps = cellResults->modifiableCellScalarResultTimesteps( address ); + timesteps->resize( 1 ); + ( *timesteps )[0].assign( cellCount, value ); +} + +// 2x2x2 grid with 50 unit cells, z = 0 at the top. PERMX = PERMY = 100 mD, PERMZ = 10 mD, NTG = 1, PORO = 0.2 +RimEclipseResultCase* createMockEclipseCase( RiaDefines::EclipseUnitSystem unitSystem ) +{ + auto* eclipseCase = new RimEclipseResultCase; + + cvf::ref caseData = new RigEclipseCaseData( eclipseCase ); + caseData->setUnitsType( unitSystem ); + cvf::ref mockReader = new RifReaderMockModel; + mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, -100 ), cvf::Vec3d( 100, 100, 0 ) ); + mockReader->setCellCounts( cvf::Vec3st( 2, 2, 2 ) ); + mockReader->enableWellData( false ); + mockReader->open( "", caseData.p() ); + caseData->mainGrid()->computeCachedData(); + + eclipseCase->setReservoirData( caseData.p() ); + + const size_t cellCount = caseData->mainGrid()->totalCellCount(); + RigCaseCellResultsData* cellResults = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + addStaticResult( cellResults, "DX", cellCount, 50.0 ); + addStaticResult( cellResults, "DY", cellCount, 50.0 ); + addStaticResult( cellResults, "DZ", cellCount, 50.0 ); + addStaticResult( cellResults, "PERMX", cellCount, 100.0 ); + addStaticResult( cellResults, "PERMY", cellCount, 100.0 ); + addStaticResult( cellResults, "PERMZ", cellCount, 10.0 ); + addStaticResult( cellResults, "NTG", cellCount, 1.0 ); + addStaticResult( cellResults, "PORO", cellCount, 0.2 ); + + return eclipseCase; +} + +// Vertical well through the two upper-left cells, perforated over the full grid height +class PerforatedWellFixture +{ +public: + explicit PerforatedWellFixture( RiaDefines::EclipseUnitSystem unitSystem ) + : unitSystem( unitSystem ) + , wellRadius( unitSystem == RiaDefines::EclipseUnitSystem::UNITS_FIELD ? 0.328 : 0.1 ) + , eclipseCase( createMockEclipseCase( unitSystem ) ) + , project( new RimProject ) + , wellPath( new RimWellPath ) + { + wellPath->setName( "TestWell" ); + wellPath->setUnitSystem( unitSystem ); + + cvf::ref geometry = new RigWellPath( { cvf::Vec3d( 25, 25, 10 ), cvf::Vec3d( 25, 25, -110 ) }, { 0.0, 120.0 } ); + wellPath->setWellPathGeometry( geometry.p() ); + project->oilFields[0]->wellPathCollection->addWellPath( wellPath ); + + auto* interval = new RimPerforationInterval; + interval->setStartAndEndMD( 10.0, 110.0 ); + interval->setDiameter( 2.0 * wellRadius ); + interval->setSkinFactor( 0.0 ); + wellPath->perforationIntervalCollection()->appendPerforation( interval ); + } + + ~PerforatedWellFixture() + { + delete eclipseCase; + project->close(); + delete project; + } + + void setNonDarcyType( RimNonDarcyPerforationParameters::NonDarcyFlowEnum type ) + { + auto* params = const_cast( wellPath->perforationIntervalCollection()->nonDarcyParameters() ); + auto* field = dynamic_cast>*>( + params->findField( "NonDarcyFlowType" ) ); + ASSERT_NE( nullptr, field ); + field->setValue( type ); + } + + void setUserDefinedDFactor( double dFactor ) + { + auto* params = const_cast( wellPath->perforationIntervalCollection()->nonDarcyParameters() ); + auto* field = dynamic_cast*>( params->findField( "UserDefinedDFactor" ) ); + ASSERT_NE( nullptr, field ); + field->setValue( dFactor ); + } + + std::vector completions() + { + return RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( wellPath, eclipseCase ); + } + + RiaDefines::EclipseUnitSystem unitSystem; + double wellRadius; + RimEclipseResultCase* eclipseCase; + RimProject* project; + RimWellPath* wellPath; +}; + RigCompletionData makeCompletion( double startMD, double endMD, std::optional completionNumber ) { RigCompletionData completion( "W", RigCompletionDataGridCell( 0, nullptr ), startMD ); @@ -49,7 +179,7 @@ std::optional numberForAllOrders( std::vector completion for ( size_t i : order ) permuted.push_back( completions[i] ); - auto number = RicWellPathExportCompletionDataFeatureImpl::completionNumberForCell( permuted ); + auto number = RicCompletionDataExportTools::completionNumberForCell( permuted ); if ( !firstSet ) { first = number; @@ -104,5 +234,134 @@ TEST( RicWellPathExportCompletionDataFeatureImpl, CompletionNumberIgnoresContrib //-------------------------------------------------------------------------------------------------- TEST( RicWellPathExportCompletionDataFeatureImpl, CompletionNumberEmptyCompletionsGivesNoNumber ) { - EXPECT_EQ( std::nullopt, RicWellPathExportCompletionDataFeatureImpl::completionNumberForCell( {} ) ); + EXPECT_EQ( std::nullopt, RicCompletionDataExportTools::completionNumberForCell( {} ) ); } + +class CompdatNonDarcyTest : public ::testing::TestWithParam +{ +}; + +//-------------------------------------------------------------------------------------------------- +/// Without non-Darcy flow, COMPDAT connections get a transmissibility but the D-factor is defaulted +//-------------------------------------------------------------------------------------------------- +TEST_P( CompdatNonDarcyTest, WithoutNonDarcyHasDefaultDFactor ) +{ + PerforatedWellFixture fixture( GetParam() ); + fixture.setNonDarcyType( RimNonDarcyPerforationParameters::NON_DARCY_NONE ); + + auto completions = fixture.completions(); + ASSERT_EQ( 2u, completions.size() ); + + for ( const auto& completion : completions ) + { + EXPECT_GT( completion.transmissibility(), 0.0 ); + EXPECT_TRUE( RigCompletionData::isDefaultValue( completion.dFactor() ) ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// User-defined well D-factor is distributed as D_conn = D_well * sum(T) / T_conn and exported negative +//-------------------------------------------------------------------------------------------------- +TEST_P( CompdatNonDarcyTest, WithUserDefinedDFactor ) +{ + PerforatedWellFixture fixture( GetParam() ); + fixture.setNonDarcyType( RimNonDarcyPerforationParameters::NON_DARCY_USER_DEFINED ); + fixture.setUserDefinedDFactor( 0.5 ); + + auto completions = fixture.completions(); + ASSERT_EQ( 2u, completions.size() ); + + double sumT = 0.0; + for ( const auto& completion : completions ) + sumT += completion.transmissibility(); + + for ( const auto& completion : completions ) + { + EXPECT_LT( completion.dFactor(), 0.0 ); + EXPECT_NEAR( -0.5 * sumT / completion.transmissibility(), completion.dFactor(), 1.0e-9 ); + } + + // Identical cells share the well D-factor equally + EXPECT_NEAR( completions[0].dFactor(), completions[1].dFactor(), 1.0e-9 ); + EXPECT_NEAR( -1.0, completions[0].dFactor(), 1.0e-9 ); +} + +//-------------------------------------------------------------------------------------------------- +/// Computed D-factor must match the D-factor equation applied to the connection's effective h and K +//-------------------------------------------------------------------------------------------------- +TEST_P( CompdatNonDarcyTest, WithComputedDFactor ) +{ + PerforatedWellFixture fixture( GetParam() ); + fixture.setNonDarcyType( RimNonDarcyPerforationParameters::NON_DARCY_COMPUTED ); + + auto completions = fixture.completions(); + ASSERT_EQ( 2u, completions.size() ); + + const auto* params = fixture.wellPath->perforationIntervalCollection()->nonDarcyParameters(); + const double alpha = RiaDefines::nonDarcyFlowAlpha( GetParam() ); + + for ( const auto& completion : completions ) + { + // Vertical well: 50 length units of well inside each cell, along K + auto data = RicTransmissibilityCalculator::calculateTransmissibilityData( fixture.eclipseCase, + fixture.wellPath, + cvf::Vec3d( 0.0, 0.0, 50.0 ), + 0.0, + fixture.wellRadius, + completion.completionDataGridCell().globalCellIndex(), + false ); + ASSERT_TRUE( data.isValid() ); + + const double effectiveK = params->gridPermeabilityScalingFactor() * data.effectiveK(); + + const double beta = RigPerforationTransmissibilityEquations::betaFactor( params->inertialCoefficientBeta0(), + effectiveK, + params->permeabilityScalingFactor(), + 0.2, + params->porosityScalingFactor() ); + + // The radius parameter is entered in meters; the field alpha expects feet + const double radiusForDFactor = ( GetParam() == RiaDefines::EclipseUnitSystem::UNITS_FIELD ) + ? params->wellRadius() * RiaEclipseUnitTools::feetPerMeter() + : params->wellRadius(); + + const double expected = RigPerforationTransmissibilityEquations::dFactor( alpha, + beta, + effectiveK, + data.effectiveH(), + radiusForDFactor, + params->relativeGasDensity(), + params->gasViscosity() ); + + ASSERT_TRUE( std::isfinite( expected ) ); + EXPECT_GT( expected, 0.0 ); + EXPECT_FALSE( RigCompletionData::isDefaultValue( completion.dFactor() ) ); + EXPECT_NEAR( -expected, completion.dFactor(), 1.0e-9 * expected ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// The connection factor itself must not depend on the non-Darcy setting +//-------------------------------------------------------------------------------------------------- +TEST_P( CompdatNonDarcyTest, TransmissibilityIndependentOfNonDarcy ) +{ + PerforatedWellFixture fixture( GetParam() ); + + fixture.setNonDarcyType( RimNonDarcyPerforationParameters::NON_DARCY_NONE ); + auto without = fixture.completions(); + + fixture.setNonDarcyType( RimNonDarcyPerforationParameters::NON_DARCY_COMPUTED ); + auto with = fixture.completions(); + + ASSERT_EQ( without.size(), with.size() ); + for ( size_t i = 0; i < with.size(); ++i ) + { + EXPECT_DOUBLE_EQ( without[i].transmissibility(), with[i].transmissibility() ); + } +} + +INSTANTIATE_TEST_SUITE_P( MetricAndField, + CompdatNonDarcyTest, + ::testing::Values( RiaDefines::EclipseUnitSystem::UNITS_METRIC, RiaDefines::EclipseUnitSystem::UNITS_FIELD ), + []( const ::testing::TestParamInfo& info ) + { return info.param == RiaDefines::EclipseUnitSystem::UNITS_FIELD ? "Field" : "Metric"; } ); diff --git a/ApplicationLibCode/UnitTests/RigPerforationTransmissibilityEquations-Test.cpp b/ApplicationLibCode/UnitTests/RigPerforationTransmissibilityEquations-Test.cpp new file mode 100644 index 0000000000..162e26dedc --- /dev/null +++ b/ApplicationLibCode/UnitTests/RigPerforationTransmissibilityEquations-Test.cpp @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2025 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 "RigPerforationTransmissibilityEquations.h" + +#include + +TEST( RigPerforationTransmissibilityEquations, BetaFactor ) +{ + // beta = inertialCoefficient * Ke^permScaling * phi^porosityScaling + double beta = RigPerforationTransmissibilityEquations::betaFactor( 2.0, 100.0, 0.5, 0.25, 2.0 ); + EXPECT_NEAR( 2.0 * 10.0 * 0.0625, beta, 1.0e-12 ); + + // Zero exponents leave only the inertial coefficient + EXPECT_DOUBLE_EQ( 3.5, RigPerforationTransmissibilityEquations::betaFactor( 3.5, 123.0, 0.0, 0.3, 0.0 ) ); +} + +TEST( RigPerforationTransmissibilityEquations, BetaFactorNegativeExponents ) +{ + // Typical non-Darcy correlation uses negative exponents: beta = C * Ke^-1.2 * phi^-0.5 + double beta = RigPerforationTransmissibilityEquations::betaFactor( 1.0e6, 100.0, -1.2, 0.25, -0.5 ); + EXPECT_NEAR( 1.0e6 * std::pow( 100.0, -1.2 ) * 2.0, beta, 1.0e-6 ); + + // Unit permeability and porosity make the exponents irrelevant + EXPECT_DOUBLE_EQ( 7.0, RigPerforationTransmissibilityEquations::betaFactor( 7.0, 1.0, -1.2, 1.0, -0.5 ) ); + + // Linear in the inertial coefficient + const double base = RigPerforationTransmissibilityEquations::betaFactor( 1.0, 50.0, -1.2, 0.2, -0.5 ); + EXPECT_NEAR( 3.0 * base, RigPerforationTransmissibilityEquations::betaFactor( 3.0, 50.0, -1.2, 0.2, -0.5 ), 1.0e-12 ); +} + +TEST( RigPerforationTransmissibilityEquations, DFactor ) +{ + // D = alpha * beta * Ke / h * 1 / rw * rho / mu + double d = RigPerforationTransmissibilityEquations::dFactor( 2.0, 3.0, 40.0, 5.0, 0.1, 0.8, 0.02 ); + EXPECT_NEAR( 2.0 * 3.0 * 8.0 * 10.0 * 40.0, d, 1.0e-9 ); +} + +TEST( RigPerforationTransmissibilityEquations, DFactorScaling ) +{ + const double base = RigPerforationTransmissibilityEquations::dFactor( 1.0, 1.0, 10.0, 2.0, 0.1, 1.0, 1.0 ); + + // Linear in unit constant, beta, permeability, density + EXPECT_NEAR( 2.0 * base, RigPerforationTransmissibilityEquations::dFactor( 2.0, 1.0, 10.0, 2.0, 0.1, 1.0, 1.0 ), 1.0e-9 ); + EXPECT_NEAR( 2.0 * base, RigPerforationTransmissibilityEquations::dFactor( 1.0, 2.0, 10.0, 2.0, 0.1, 1.0, 1.0 ), 1.0e-9 ); + EXPECT_NEAR( 2.0 * base, RigPerforationTransmissibilityEquations::dFactor( 1.0, 1.0, 20.0, 2.0, 0.1, 1.0, 1.0 ), 1.0e-9 ); + EXPECT_NEAR( 2.0 * base, RigPerforationTransmissibilityEquations::dFactor( 1.0, 1.0, 10.0, 2.0, 0.1, 2.0, 1.0 ), 1.0e-9 ); + + // Inversely proportional to length, well radius and viscosity + EXPECT_NEAR( 0.5 * base, RigPerforationTransmissibilityEquations::dFactor( 1.0, 1.0, 10.0, 4.0, 0.1, 1.0, 1.0 ), 1.0e-9 ); + EXPECT_NEAR( 0.5 * base, RigPerforationTransmissibilityEquations::dFactor( 1.0, 1.0, 10.0, 2.0, 0.2, 1.0, 1.0 ), 1.0e-9 ); + EXPECT_NEAR( 0.5 * base, RigPerforationTransmissibilityEquations::dFactor( 1.0, 1.0, 10.0, 2.0, 0.1, 1.0, 2.0 ), 1.0e-9 ); +} diff --git a/ApplicationLibCode/UnitTests/RimFractureTemplateNonDarcy-Test.cpp b/ApplicationLibCode/UnitTests/RimFractureTemplateNonDarcy-Test.cpp new file mode 100644 index 0000000000..1a31b2ec69 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RimFractureTemplateNonDarcy-Test.cpp @@ -0,0 +1,205 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RiaDefines.h" +#include "RiaFractureDefines.h" + +#include "RimEllipseFractureTemplate.h" + +#include +#include + +//-------------------------------------------------------------------------------------------------- +/// Exposes protected settings of the template, so that the values reported by +/// wellFractureIntersectionData() can be verified through the public compute functions +//-------------------------------------------------------------------------------------------------- +class TestEllipseFractureTemplate : public RimEllipseFractureTemplate +{ +public: + explicit TestEllipseFractureTemplate( RiaDefines::EclipseUnitSystem unit ) + { + setUnitSystem( unit ); + setDefaultValuesFromUnit(); + } + + void useFractureData() + { + m_fractureWidthType = WIDTH_FROM_FRACTURE; + m_permeabilityType = CONDUCTIVITY_FROM_FRACTURE; + m_betaFactorType = USER_DEFINED_BETA_FACTOR; + m_relativePermeability = 1.0; + } + + void useUserDefinedWidth( double width ) + { + m_fractureWidthType = USER_DEFINED_WIDTH; + m_fractureWidth = width; + } + + void useComputedDFactor( double inertialCoefficient ) + { + m_nonDarcyFlowType = NON_DARCY_COMPUTED; + m_inertialCoefficient = inertialCoefficient; + } + + void useUserDefinedDFactor( double dFactor ) + { + m_nonDarcyFlowType = NON_DARCY_USER_DEFINED; + m_userDefinedDFactor = dFactor; + } + + void setRelativePermeability( double value ) { m_relativePermeability = value; } + void setOrientation( FracOrientationEnum orientation ) { m_orientationType = orientation; } + double wellDiameterValue() const { return m_wellDiameter; } + double gasDensity() const { return m_relativeGasDensity; } + double gasViscosityValue() const { return m_gasViscosity; } +}; + +//-------------------------------------------------------------------------------------------------- +/// Ellipse templates report the user entered width and permeability as intersection data +//-------------------------------------------------------------------------------------------------- +TEST( RimFractureTemplateNonDarcy, EllipseIntersectionDataMetric ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + + // Metric defaults: width 0.01 m, permeability 100000 mD + EXPECT_DOUBLE_EQ( 0.01, tmpl.computeFractureWidth( nullptr ) ); + EXPECT_DOUBLE_EQ( 100000.0, tmpl.computeEffectivePermeability( nullptr ) ); + EXPECT_NEAR( 1000.0, tmpl.computeKh( nullptr ), 1.0e-9 ); +} + +TEST( RimFractureTemplateNonDarcy, EllipseIntersectionDataField ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_FIELD ); + tmpl.useFractureData(); + + // Field defaults: width 0.5 inches (reported in feet), permeability 80000 mD + EXPECT_NEAR( 0.5 / 12.0, tmpl.computeFractureWidth( nullptr ), 1.0e-12 ); + EXPECT_DOUBLE_EQ( 80000.0, tmpl.computeEffectivePermeability( nullptr ) ); + EXPECT_NEAR( 80000.0 * 0.5 / 12.0, tmpl.computeKh( nullptr ), 1.0e-9 ); +} + +TEST( RimFractureTemplateNonDarcy, RelativePermeabilityScalesEffectivePermeability ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + tmpl.setRelativePermeability( 0.25 ); + + EXPECT_DOUBLE_EQ( 25000.0, tmpl.computeEffectivePermeability( nullptr ) ); + EXPECT_NEAR( 250.0, tmpl.computeKh( nullptr ), 1.0e-9 ); +} + +TEST( RimFractureTemplateNonDarcy, UserDefinedWidthOverridesIntersectionWidth ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + tmpl.useUserDefinedWidth( 0.05 ); + + EXPECT_DOUBLE_EQ( 0.05, tmpl.computeFractureWidth( nullptr ) ); + EXPECT_NEAR( 100000.0 * 0.05, tmpl.computeKh( nullptr ), 1.0e-9 ); +} + +//-------------------------------------------------------------------------------------------------- +/// D = alpha * beta * Ke / h * 1 / rw * gamma / mu, using the intersection data of the template +//-------------------------------------------------------------------------------------------------- +TEST( RimFractureTemplateNonDarcy, ComputedDFactorMatchesEquation ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + tmpl.useComputedDFactor( 0.5 ); + + const double alpha = RiaDefines::nonDarcyFlowAlpha( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + const double radius = tmpl.wellDiameterValue() / 2.0; + const double ke = 100000.0; + const double h = 0.01; + + const double expected = alpha * 0.5 * ke * tmpl.gasDensity() / ( h * radius * tmpl.gasViscosityValue() ); + + EXPECT_NEAR( expected, tmpl.computeDFactor( nullptr ), expected * 1.0e-12 ); + EXPECT_NEAR( radius, tmpl.computeWellRadiusForDFactorCalculation( nullptr ), 1.0e-12 ); +} + +TEST( RimFractureTemplateNonDarcy, ComputedDFactorUsesUnitDependentAlpha ) +{ + TestEllipseFractureTemplate metric( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + metric.useFractureData(); + metric.useComputedDFactor( 1.0 ); + metric.useUserDefinedWidth( 0.01 ); + + TestEllipseFractureTemplate field( RiaDefines::EclipseUnitSystem::UNITS_FIELD ); + field.useFractureData(); + field.useComputedDFactor( 1.0 ); + field.useUserDefinedWidth( 0.01 ); + + // Make everything but alpha, permeability and well radius equal between the two templates + const double ratio = ( field.computeDFactor( nullptr ) / metric.computeDFactor( nullptr ) ); + + // The field well diameter is given in inches, but the radius used in the D-factor must be in feet + EXPECT_NEAR( field.wellDiameterValue() / 2.0 / 12.0, field.computeWellRadiusForDFactorCalculation( nullptr ), 1.0e-12 ); + + const double alphaRatio = RiaDefines::nonDarcyFlowAlpha( RiaDefines::EclipseUnitSystem::UNITS_FIELD ) / + RiaDefines::nonDarcyFlowAlpha( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + const double permRatio = field.computeEffectivePermeability( nullptr ) / metric.computeEffectivePermeability( nullptr ); + const double metricRadius = metric.wellDiameterValue() / 2.0; // [m] + const double fieldRadius = field.wellDiameterValue() / 2.0 / 12.0; // inches -> [ft] + const double radiusRatio = metricRadius / fieldRadius; + + EXPECT_NEAR( alphaRatio * permRatio * radiusRatio, ratio, ratio * 1.0e-12 ); +} + +TEST( RimFractureTemplateNonDarcy, DFactorScaleFactorAndAlongWellPathCorrection ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + tmpl.useComputedDFactor( 0.5 ); + + const double base = tmpl.computeDFactor( nullptr ); + + tmpl.setScaleFactors( 1.0, 1.0, 3.0, 1.0 ); + EXPECT_NEAR( 3.0 * base, tmpl.computeDFactor( nullptr ), base * 1.0e-12 ); + + // Linear inflow correction factor of 1.2 for fractures along the well path + tmpl.setScaleFactors( 1.0, 1.0, 1.0, 1.0 ); + tmpl.setOrientation( RimFractureTemplate::ALONG_WELL_PATH ); + EXPECT_NEAR( 1.2 * base, tmpl.computeDFactor( nullptr ), base * 1.0e-12 ); +} + +TEST( RimFractureTemplateNonDarcy, UserDefinedDFactorIsScaledButNotComputed ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + tmpl.useUserDefinedDFactor( 0.125 ); + + EXPECT_DOUBLE_EQ( 0.125, tmpl.computeDFactor( nullptr ) ); + + tmpl.setScaleFactors( 1.0, 1.0, 2.0, 1.0 ); + EXPECT_DOUBLE_EQ( 0.25, tmpl.computeDFactor( nullptr ) ); +} + +TEST( RimFractureTemplateNonDarcy, ZeroWidthGivesInfiniteDFactor ) +{ + TestEllipseFractureTemplate tmpl( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + tmpl.useFractureData(); + tmpl.useComputedDFactor( 0.5 ); + tmpl.useUserDefinedWidth( 0.0 ); + + EXPECT_EQ( std::numeric_limits::infinity(), tmpl.computeDFactor( nullptr ) ); +} diff --git a/ApplicationLibCode/UnitTests/RimStimPlanFractureTemplate-Test.cpp b/ApplicationLibCode/UnitTests/RimStimPlanFractureTemplate-Test.cpp new file mode 100644 index 0000000000..ae3fd427b1 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RimStimPlanFractureTemplate-Test.cpp @@ -0,0 +1,151 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RiaTestDataDirectory.h" + +#include "Well/RigWellPath.h" + +#include "RimStimPlanFractureTemplate.h" +#include "RimWellPath.h" +#include "RimWellPathFracture.h" +#include "RimWellPathFractureCollection.h" + +#include + +static const QString STIMPLAN_TEMPLATE_TEST_DATA_DIRECTORY = QString( "%1/RimStimPlanFractureTemplate/" ).arg( TEST_DATA_DIR ); +static const QString STIMPLAN_XML_READER_TEST_DATA_DIRECTORY = QString( "%1/RifStimPlanXmlReader/" ).arg( TEST_DATA_DIR ); + +//-------------------------------------------------------------------------------------------------- +/// Gives access to the protected settings needed to load a template from file +//-------------------------------------------------------------------------------------------------- +class TestStimPlanFractureTemplate : public RimStimPlanFractureTemplate +{ +public: + explicit TestStimPlanFractureTemplate( const QString& fileName ) + { + setFileName( fileName ); + loadDataAndUpdate(); + m_fractureWidthType = WIDTH_FROM_FRACTURE; + m_permeabilityType = CONDUCTIVITY_FROM_FRACTURE; + } + + void useBetaFromFile() { m_betaFactorType = BETA_FACTOR_FROM_FRACTURE; } + void setPerforationLengthValue( double length ) { m_perforationLength = length; } + void setOrientation( FracOrientationEnum orientation ) { m_orientationType = orientation; } +}; + +//-------------------------------------------------------------------------------------------------- +/// Width, conductivity and beta at the well position are taken from the fracture grid, and the +/// beta factor is converted from file units to Forchheimer units +//-------------------------------------------------------------------------------------------------- +TEST( RimStimPlanFractureTemplateTest, IntersectionDataAtWellCenterFromFile ) +{ + TestStimPlanFractureTemplate tmpl( STIMPLAN_TEMPLATE_TEST_DATA_DIRECTORY + "uniform_width_beta.xml" ); + tmpl.useBetaFromFile(); + + RimWellPathFracture fracture; + fracture.setFractureTemplate( &tmpl ); + + ASSERT_TRUE( fracture.fractureGrid() != nullptr ); + + // Width 0.01 m and conductivity 300 md-m gives permeability 30000 mD + EXPECT_NEAR( 0.01, tmpl.computeFractureWidth( &fracture ), 1.0e-12 ); + EXPECT_NEAR( 30000.0, tmpl.computeEffectivePermeability( &fracture ), 1.0e-6 ); + EXPECT_NEAR( 300.0, tmpl.computeKh( &fracture ), 1.0e-9 ); + + // 2.0265e8 [/m] divided by 1.01325e8 gives 2.0 Forchheimer units + EXPECT_NEAR( 2.0, tmpl.getOrComputeBetaFactor( &fracture ), 1.0e-9 ); +} + +TEST( RimStimPlanFractureTemplateTest, BetaFactorIsUserDefinedByDefault ) +{ + TestStimPlanFractureTemplate tmpl( STIMPLAN_TEMPLATE_TEST_DATA_DIRECTORY + "uniform_width_beta.xml" ); + + RimWellPathFracture fracture; + fracture.setFractureTemplate( &tmpl ); + + EXPECT_NEAR( 0.006083236, tmpl.getOrComputeBetaFactor( &fracture ), 1.0e-12 ); +} + +//-------------------------------------------------------------------------------------------------- +/// Without a fracture instance there is no intersection data: width is zero and beta is undefined +//-------------------------------------------------------------------------------------------------- +TEST( RimStimPlanFractureTemplateTest, NoFractureInstanceGivesNoIntersectionData ) +{ + TestStimPlanFractureTemplate tmpl( STIMPLAN_TEMPLATE_TEST_DATA_DIRECTORY + "uniform_width_beta.xml" ); + tmpl.useBetaFromFile(); + + EXPECT_DOUBLE_EQ( 0.0, tmpl.computeFractureWidth( nullptr ) ); + EXPECT_TRUE( std::isinf( tmpl.getOrComputeBetaFactor( nullptr ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// A file without width values has no width data, and the conductivity at the well is used directly +//-------------------------------------------------------------------------------------------------- +TEST( RimStimPlanFractureTemplateTest, FileWithoutWidthUsesConductivityDirectly ) +{ + TestStimPlanFractureTemplate tmpl( STIMPLAN_XML_READER_TEST_DATA_DIRECTORY + "small_fracture.xml" ); + + RimWellPathFracture fracture; + fracture.setFractureTemplate( &tmpl ); + + ASSERT_TRUE( fracture.fractureGrid() != nullptr ); + + // The conductivity at the well center is 301.7 md-m in the file + EXPECT_NEAR( 301.7, tmpl.computeKh( &fracture ), 1.0e-9 ); +} + +//-------------------------------------------------------------------------------------------------- +/// For fractures along the well path, the values are averaged along the part of the well path +/// that intersects the fracture +//-------------------------------------------------------------------------------------------------- +TEST( RimStimPlanFractureTemplateTest, IntersectionDataAlongWellPath ) +{ + // Horizontal well along the X axis at depth 2015 m, with measured depth equal to the X coordinate + 500 + std::vector wellPathPoints; + std::vector measuredDepths; + for ( int i = 0; i <= 20; i++ ) + { + const double x = -500.0 + i * 50.0; + wellPathPoints.push_back( cvf::Vec3d( x, 0.0, -2015.0 ) ); + measuredDepths.push_back( x + 500.0 ); + } + + RimWellPath wellPath; + wellPath.setWellPathGeometry( new RigWellPath( wellPathPoints, measuredDepths ) ); + + TestStimPlanFractureTemplate tmpl( STIMPLAN_TEMPLATE_TEST_DATA_DIRECTORY + "uniform_width_beta.xml" ); + tmpl.useBetaFromFile(); + tmpl.setOrientation( RimFractureTemplate::ALONG_WELL_PATH ); + tmpl.setPerforationLengthValue( 80.0 ); + + auto fracture = new RimWellPathFracture; + wellPath.fractureCollection()->addFracture( fracture ); + fracture->setFractureUnit( RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + fracture->setFractureTemplate( &tmpl ); + fracture->setMeasuredDepth( 500.0 ); + + ASSERT_TRUE( fracture->fractureGrid() != nullptr ); + + EXPECT_NEAR( 0.01, tmpl.computeFractureWidth( fracture ), 1.0e-9 ); + EXPECT_NEAR( 30000.0, tmpl.computeEffectivePermeability( fracture ), 1.0e-3 ); + EXPECT_NEAR( 300.0, tmpl.computeKh( fracture ), 1.0e-6 ); + EXPECT_NEAR( 2.0, tmpl.getOrComputeBetaFactor( fracture ), 1.0e-9 ); +} diff --git a/ApplicationLibCode/UnitTests/TestData/RimStimPlanFractureTemplate/uniform_width_beta.xml b/ApplicationLibCode/UnitTests/TestData/RimStimPlanFractureTemplate/uniform_width_beta.xml new file mode 100644 index 0000000000..87ae9ae19a --- /dev/null +++ b/ApplicationLibCode/UnitTests/TestData/RimStimPlanFractureTemplate/uniform_width_beta.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + +