diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp index 2fb4d4f27f..bd29a64d03 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp @@ -151,6 +151,9 @@ void RicExportEclipseSectorModelFeature::executeCommand( RimEclipseView* if ( !bordnumVisibility.isNull() ) { RigEclipseResultTools::generateBorderResult( view->eclipseCase(), bordnumVisibility, "BORDNUM" ); + + // Generate OPERNUM result based on BORDNUM (border cells get max existing OPERNUM + 1) + RigEclipseResultTools::generateOperNumResult( view->eclipseCase() ); } if ( exportSettings.exportParameters() != RicExportEclipseSectorModelUi::EXPORT_NO_RESULTS ) @@ -164,6 +167,12 @@ void RicExportEclipseSectorModelFeature::executeCommand( RimEclipseView* keywords.push_back( "BORDNUM" ); } + // Automatically add OPERNUM to the keywords list if not already present + if ( std::find( keywords.begin(), keywords.end(), "OPERNUM" ) == keywords.end() ) + { + keywords.push_back( "OPERNUM" ); + } + if ( exportSettings.exportParameters == RicExportEclipseSectorModelUi::EXPORT_TO_SEPARATE_FILE_PER_RESULT ) { QFileInfo info( exportSettings.exportGridFilename() ); diff --git a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp index 13aeb8fe7e..28860f9f93 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.cpp @@ -102,4 +102,126 @@ void generateBorderResult( RimEclipseCase* eclipseCase, cvf::refupdateConnectedEditors(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void generateOperNumResult( RimEclipseCase* eclipseCase, int borderCellValue ) +{ + if ( eclipseCase == nullptr ) return; + + // Auto-determine border cell value if not specified + if ( borderCellValue == -1 ) + { + int maxOperNum = findMaxOperNumValue( eclipseCase ); + borderCellValue = maxOperNum + 1; + } + + auto activeReservoirCellIdxs = + eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices(); + int numActiveCells = static_cast( activeReservoirCellIdxs.size() ); + + std::vector result; + result.resize( numActiveCells, 0 ); + + // Check if OPERNUM already exists - if so, keep existing values. + RigEclipseResultAddress operNumAddrNative( RiaDefines::ResultCatType::STATIC_NATIVE, RiaDefines::ResultDataType::INTEGER, "OPERNUM" ); + RigEclipseResultAddress operNumAddrGenerated( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "OPERNUM" ); + auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + + RigEclipseResultAddress operNumAddr; + bool hasExistingOperNum = false; + + if ( resultsData->hasResultEntry( operNumAddrNative ) ) + { + operNumAddr = operNumAddrNative; + hasExistingOperNum = true; + } + else if ( resultsData->hasResultEntry( operNumAddrGenerated ) ) + { + operNumAddr = operNumAddrGenerated; + hasExistingOperNum = true; + } + + if ( hasExistingOperNum ) + { + resultsData->ensureKnownResultLoaded( operNumAddr ); + auto existingValues = resultsData->cellScalarResults( operNumAddr ); + if ( !existingValues.empty() ) + { + for ( int i = 0; i < numActiveCells; i++ ) + { + result[i] = static_cast( existingValues[0][i] ); + } + } + } + + // Check if BORDNUM exists to modify border cells + RigEclipseResultAddress bordNumAddr( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "BORDNUM" ); + if ( resultsData->hasResultEntry( bordNumAddr ) ) + { + resultsData->ensureKnownResultLoaded( bordNumAddr ); + auto bordNumValues = resultsData->cellScalarResults( bordNumAddr ); + if ( !bordNumValues.empty() ) + { + for ( int i = 0; i < numActiveCells; i++ ) + { + // If BORDNUM = 1 (BORDER_CELL), assign the border cell value + if ( static_cast( bordNumValues[0][i] ) == BorderType::BORDER_CELL ) + { + result[i] = borderCellValue; + } + } + } + } + + RigEclipseResultTools::createResultVector( *eclipseCase, "OPERNUM", result ); + + eclipseCase->updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int findMaxOperNumValue( RimEclipseCase* eclipseCase ) +{ + if ( eclipseCase == nullptr ) return 0; + + auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + if ( !resultsData ) return 0; + + // Try to find OPERNUM in both STATIC_NATIVE (from file) and GENERATED (created by us) categories + RigEclipseResultAddress operNumAddrNative( RiaDefines::ResultCatType::STATIC_NATIVE, RiaDefines::ResultDataType::INTEGER, "OPERNUM" ); + RigEclipseResultAddress operNumAddrGenerated( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "OPERNUM" ); + + RigEclipseResultAddress operNumAddr; + bool hasOperNum = false; + + if ( resultsData->hasResultEntry( operNumAddrNative ) ) + { + operNumAddr = operNumAddrNative; + hasOperNum = true; + } + else if ( resultsData->hasResultEntry( operNumAddrGenerated ) ) + { + operNumAddr = operNumAddrGenerated; + hasOperNum = true; + } + + if ( !hasOperNum ) return 0; + + resultsData->ensureKnownResultLoaded( operNumAddr ); + auto operNumValues = resultsData->cellScalarResults( operNumAddr ); + if ( operNumValues.empty() ) return 0; + + // Find maximum value + int maxValue = 0; + for ( double value : operNumValues[0] ) + { + int intValue = static_cast( value ); + maxValue = std::max( maxValue, intValue ); + } + + return maxValue; +} + } // namespace RigEclipseResultTools diff --git a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h index 1dd2d6d65f..c87f621a9f 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h +++ b/ApplicationLibCode/ReservoirDataModel/RigEclipseResultTools.h @@ -39,4 +39,8 @@ void createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, void generateBorderResult( RimEclipseCase* eclipseCase, cvf::ref customVisibility, const QString& resultName = "BORDER" ); +void generateOperNumResult( RimEclipseCase* eclipseCase, int borderCellValue = -1 ); + +int findMaxOperNumValue( RimEclipseCase* eclipseCase ); + } // namespace RigEclipseResultTools diff --git a/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp b/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp index c0a5d9ab1b..4254d650d3 100644 --- a/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp +++ b/ApplicationLibCode/UnitTests/RigEclipseCaseDataTools-Test.cpp @@ -25,6 +25,7 @@ #include "RimEclipseResultCase.h" +#include "RigActiveCellInfo.h" #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RigEclipseCaseDataTools.h" @@ -496,3 +497,148 @@ TEST( RigEclipseCaseDataToolsTest, GenerateBorderResultFromIjkBounds ) int totalCells = invisibleCells + borderCells + interiorCells; ASSERT_EQ( totalCells, static_cast( resultVector.size() ) ) << "Total should match result vector size"; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RigEclipseCaseDataToolsTest, GenerateOperNumResultFromBorderResult ) +{ + // Load the BRUGGE test model using RifReaderEclipseOutput + QDir baseFolder( TEST_MODEL_DIR ); + bool subFolderExists = baseFolder.cd( "Case_with_10_timesteps/Real0" ); + ASSERT_TRUE( subFolderExists ) << "Could not find test model directory"; + + QString filename( "BRUGGE_0000.EGRID" ); + QString filePath = baseFolder.absoluteFilePath( filename ); + ASSERT_TRUE( QFile::exists( filePath ) ) << "BRUGGE test model file does not exist: " << filePath.toStdString(); + + std::unique_ptr resultCase( new RimEclipseResultCase ); + cvf::ref eclipseCase = new RigEclipseCaseData( resultCase.get() ); + + cvf::ref readerInterfaceEcl = new RifReaderEclipseOutput; + bool success = readerInterfaceEcl->open( filePath, eclipseCase.p() ); + ASSERT_TRUE( success ) << "Could not load BRUGGE test model"; + + resultCase->setReservoirData( eclipseCase.p() ); + + // Define IJK bounds for testing + cvf::Vec3st minIjk( 20, 20, 5 ); + cvf::Vec3st maxIjk( 30, 30, 8 ); + + // Create visibility from IJK bounds + auto visibility = RigEclipseCaseDataTools::createVisibilityFromIjkBounds( eclipseCase.p(), minIjk, maxIjk ); + ASSERT_FALSE( visibility.isNull() ) << "Visibility should be created successfully"; + + // Generate border result first (required for OPERNUM generation) + RigEclipseResultTools::generateBorderResult( resultCase.get(), visibility, "BORDNUM" ); + + // Verify BORDNUM was created + auto resultsData = resultCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); + ASSERT_NE( resultsData, nullptr ) << "Results data should not be null"; + + RigEclipseResultAddress bordnumAddress( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "BORDNUM" ); + ASSERT_TRUE( resultsData->hasResultEntry( bordnumAddress ) ) << "BORDNUM result should exist"; + + // Test case 1: Generate OPERNUM with automatic border cell value (no existing OPERNUM, should get 1) + RigEclipseResultTools::generateOperNumResult( resultCase.get() ); + + // Verify OPERNUM was created + RigEclipseResultAddress opernumAddress( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "OPERNUM" ); + ASSERT_TRUE( resultsData->hasResultEntry( opernumAddress ) ) << "OPERNUM result should exist"; + + // Get the result data + const std::vector& bordnumVector = resultsData->cellScalarResults( bordnumAddress, 0 ); + const std::vector& opernumVector = resultsData->cellScalarResults( opernumAddress, 0 ); + + ASSERT_EQ( bordnumVector.size(), opernumVector.size() ) << "BORDNUM and OPERNUM should have same size"; + ASSERT_GT( opernumVector.size(), 0 ) << "OPERNUM vector should not be empty"; + + // Count cells with different values and verify the logic + int borderCellsWithOperValue = 0; // BORDNUM=1 cells that should get OPERNUM=1 (max 0 + 1) + int defaultOperCells = 0; // BORDNUM!=1 cells that should get OPERNUM=0 + int borderCellsTotal = 0; // Total BORDNUM=1 cells + + for ( size_t i = 0; i < bordnumVector.size(); ++i ) + { + int bordnumValue = static_cast( bordnumVector[i] ); + int opernumValue = static_cast( opernumVector[i] ); + + if ( bordnumValue == RigEclipseResultTools::BorderType::BORDER_CELL ) + { + borderCellsTotal++; + ASSERT_EQ( opernumValue, 1 ) << "Border cells (BORDNUM=1) should have OPERNUM=1 at index " << i; + borderCellsWithOperValue++; + } + else + { + ASSERT_EQ( opernumValue, 0 ) << "Non-border cells should have OPERNUM=0 at index " << i; + defaultOperCells++; + } + } + + // Verify we have the expected distribution + ASSERT_GT( borderCellsTotal, 0 ) << "Should have border cells (BORDNUM=1)"; + ASSERT_EQ( borderCellsWithOperValue, borderCellsTotal ) << "All border cells should get OPERNUM=1"; + ASSERT_GT( defaultOperCells, 0 ) << "Should have non-border cells with OPERNUM=0"; + + // Test case 2: Test automatic max value detection with existing OPERNUM + // First create a custom OPERNUM result with some predefined values + auto activeReservoirCellIdxs = eclipseCase->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices(); + int numActiveCells = static_cast( activeReservoirCellIdxs.size() ); + + std::vector customOperValues; + customOperValues.resize( numActiveCells, 5 ); // Set all to 5 initially + // Add some higher values to test max detection + if ( numActiveCells > 10 ) + { + customOperValues[5] = 15; // Maximum will be 15 + customOperValues[8] = 12; + } + + // Create the OPERNUM result directly + RigEclipseResultTools::createResultVector( *resultCase, "OPERNUM", customOperValues ); + + // Test case 3: Test the utility function directly (before regeneration) + int maxOperValue = RigEclipseResultTools::findMaxOperNumValue( resultCase.get() ); + ASSERT_EQ( maxOperValue, 15 ) << "Max OPERNUM value should be 15"; + + // Now generate OPERNUM again - should use max value + 1 = 16 for border cells + RigEclipseResultTools::generateOperNumResult( resultCase.get() ); + + // Get updated result + const std::vector& updatedOpernumVector = resultsData->cellScalarResults( opernumAddress, 0 ); + + // Verify the behavior + int preservedValues = 0; + int modifiedValues = 0; + + for ( size_t i = 0; i < bordnumVector.size(); ++i ) + { + int bordnumValue = static_cast( bordnumVector[i] ); + int opernumValue = static_cast( updatedOpernumVector[i] ); + + if ( bordnumValue == RigEclipseResultTools::BorderType::BORDER_CELL ) + { + ASSERT_EQ( opernumValue, 16 ) << "Border cells should be updated to OPERNUM=16 (max 15 + 1) at index " << i; + modifiedValues++; + } + else + { + // Non-border cells should preserve their original values + if ( numActiveCells > 10 && i == 5 ) + ASSERT_EQ( opernumValue, 15 ) << "Non-border cells should preserve original max value at index " << i; + else if ( numActiveCells > 10 && i == 8 ) + ASSERT_EQ( opernumValue, 12 ) << "Non-border cells should preserve original value at index " << i; + else + ASSERT_EQ( opernumValue, 5 ) << "Non-border cells should preserve original value at index " << i; + preservedValues++; + } + } + + ASSERT_GT( modifiedValues, 0 ) << "Should have modified some border cells"; + ASSERT_GT( preservedValues, 0 ) << "Should have preserved some non-border cells"; + + // Total should match vector size + int totalChecked = modifiedValues + preservedValues; + ASSERT_EQ( totalChecked, static_cast( updatedOpernumVector.size() ) ) << "Should check all cells"; +}