From 7a36ea9932cb72ac218d55a3ee73e1fa0e45e335 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 12 Nov 2025 14:08:46 +0100 Subject: [PATCH] Add EQUALS keyword processing. --- .../RicExportEclipseSectorModelFeature.cpp | 19 +- .../RicExportEclipseSectorModelFeature.h | 6 +- ApplicationLibCode/UnitTests/CMakeLists.txt | 1 + ...icExportEclipseSectorModelFeature-Test.cpp | 212 ++++++++++++++++++ 4 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 ApplicationLibCode/UnitTests/RicExportEclipseSectorModelFeature-Test.cpp diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp index c8160b1393..fa418b2078 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.cpp @@ -605,7 +605,8 @@ std::expected RicExportEclipseSectorModelFeature::replaceEqualsKe for ( size_t recordIdx = 0; recordIdx < kw.size(); ++recordIdx ) { const auto& record = kw.getRecord( recordIdx ); - auto result = processEqualsRecord( record, exportSettings ); + auto result = + processEqualsRecord( record, exportSettings.min(), exportSettings.max(), exportSettings.refinement() ); if ( result ) { @@ -1040,8 +1041,10 @@ std::expected //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::expected - RicExportEclipseSectorModelFeature::processEqualsRecord( const Opm::DeckRecord& record, const RicExportEclipseSectorModelUi& exportSettings ) +std::expected RicExportEclipseSectorModelFeature::processEqualsRecord( const Opm::DeckRecord& record, + const cvf::Vec3st& min, + const cvf::Vec3st& max, + const cvf::Vec3st& refinement ) { // EQUALS format: FIELD VALUE I1 I2 J1 J2 K1 K2 // Items: 0=FIELD, 1=VALUE, 2=I1, 3=I2, 4=J1, 5=J2, 6=K1, 7=K2 @@ -1065,9 +1068,9 @@ std::expected int origK1 = record.getItem( 6 ).get( 0 ) - 1; int origK2 = record.getItem( 7 ).get( 0 ) - 1; - // Create bounding boxes + // Create bounding boxes (both use inclusive min/max coordinates) RigBoundingBoxIjk equalsBox( cvf::Vec3st( origI1, origJ1, origK1 ), cvf::Vec3st( origI2, origJ2, origK2 ) ); - RigBoundingBoxIjk sectorBox( exportSettings.min(), exportSettings.max() - cvf::Vec3st( 1, 1, 1 ) ); + RigBoundingBoxIjk sectorBox( min, max ); // Check if boxes overlap and get intersection auto intersection = equalsBox.intersection( sectorBox ); @@ -1113,10 +1116,8 @@ std::expected .arg( corner2.z() + 1 ) ); } - auto transformResult1 = - RigGridExportAdapter::transformIjkToSectorCoordinates( corner1, exportSettings.min(), exportSettings.max(), exportSettings.refinement() ); - auto transformResult2 = - RigGridExportAdapter::transformIjkToSectorCoordinates( corner2, exportSettings.min(), exportSettings.max(), exportSettings.refinement() ); + auto transformResult1 = RigGridExportAdapter::transformIjkToSectorCoordinates( corner1, min, max, refinement ); + auto transformResult2 = RigGridExportAdapter::transformIjkToSectorCoordinates( corner2, min, max, refinement ); if ( !transformResult1 ) { diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.h b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.h index f467ba5104..15e02ec591 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.h +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportEclipseSectorModelFeature.h @@ -50,6 +50,9 @@ public: static std::pair getVisibleCellRange( RimEclipseView* view, const cvf::UByteArray& cellVisibility ); + static std::expected + processEqualsRecord( const Opm::DeckRecord& record, const cvf::Vec3st& min, const cvf::Vec3st& max, const cvf::Vec3st& refinement ); + protected: bool isCommandEnabled() const override; void onActionTriggered( bool isChecked ) override; @@ -107,7 +110,4 @@ private: const std::string& wellName, bool isWellNameRecord, const RicExportEclipseSectorModelUi& exportSettings ); - - static std::expected processEqualsRecord( const Opm::DeckRecord& record, - const RicExportEclipseSectorModelUi& exportSettings ); }; diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 10477a5eae..26e2c989f6 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -8,6 +8,7 @@ endif(MSVC) set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/cvfGeometryTools-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/Ert-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicExportEclipseSectorModelFeature-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RigBoundingBoxIjk-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifcCommandCore-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifEclipseInputFileTools-Test.cpp diff --git a/ApplicationLibCode/UnitTests/RicExportEclipseSectorModelFeature-Test.cpp b/ApplicationLibCode/UnitTests/RicExportEclipseSectorModelFeature-Test.cpp new file mode 100644 index 0000000000..84d0bb16e8 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RicExportEclipseSectorModelFeature-Test.cpp @@ -0,0 +1,212 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "ExportCommands/RicExportEclipseSectorModelFeature.h" + +#include "RifOpmDeckTools.h" + +#include "opm/input/eclipse/Deck/DeckItem.hpp" +#include "opm/input/eclipse/Deck/DeckRecord.hpp" + +#include +#include + +//-------------------------------------------------------------------------------------------------- +/// Helper to create an EQUALS DeckRecord +/// EQUALS format: FIELD VALUE I1 I2 J1 J2 K1 K2 (1-based Eclipse coordinates) +//-------------------------------------------------------------------------------------------------- +static Opm::DeckRecord createEqualsRecord( const std::string& fieldName, int value, int i1, int i2, int j1, int j2, int k1, int k2 ) +{ + std::vector items; + items.push_back( RifOpmDeckTools::item( "FIELD", fieldName ) ); + items.push_back( RifOpmDeckTools::item( "VALUE", value ) ); + items.push_back( RifOpmDeckTools::item( "I1", i1 ) ); + items.push_back( RifOpmDeckTools::item( "I2", i2 ) ); + items.push_back( RifOpmDeckTools::item( "J1", j1 ) ); + items.push_back( RifOpmDeckTools::item( "J2", j2 ) ); + items.push_back( RifOpmDeckTools::item( "K1", k1 ) ); + items.push_back( RifOpmDeckTools::item( "K2", k2 ) ); + + return Opm::DeckRecord{ std::move( items ) }; +} + +//-------------------------------------------------------------------------------------------------- +/// Test EQUALS record with no overlap - should return error +//-------------------------------------------------------------------------------------------------- +TEST( RicExportEclipseSectorModelFeature, ProcessEqualsRecord_NoOverlap ) +{ + // Sector: min=(0, 15, 0), max=(19, 29, 9) (inclusive, cells [0-19, 15-29, 0-9]) + cvf::Vec3st min( 0, 15, 0 ); + cvf::Vec3st max( 19, 29, 9 ); + cvf::Vec3st refinement( 1, 1, 1 ); + + // EQUALS record: FIPNUM 1 1 20 1 14 1 10 (1-based Eclipse) + // Converts to 0-based: I[0-19], J[0-13], K[0-9] + // Sector J is [15-29], so no overlap in J dimension + auto record = createEqualsRecord( "FIPNUM", 1, 1, 20, 1, 14, 1, 10 ); + + auto result = RicExportEclipseSectorModelFeature::processEqualsRecord( record, min, max, refinement ); + + EXPECT_FALSE( result.has_value() ); + EXPECT_TRUE( result.error().contains( "does not overlap" ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test EQUALS record with partial overlap requiring clamping +//-------------------------------------------------------------------------------------------------- +TEST( RicExportEclipseSectorModelFeature, ProcessEqualsRecord_PartialOverlapWithClamping ) +{ + // Sector: min=(0, 15, 0), max=(19, 29, 9) (inclusive, cells [0-19, 15-29, 0-9]) + cvf::Vec3st min( 0, 15, 0 ); + cvf::Vec3st max( 19, 29, 9 ); + cvf::Vec3st refinement( 1, 1, 1 ); + + // EQUALS record: FIPNUM 2 1 20 15 30 1 10 (1-based Eclipse) + // Converts to 0-based: I[0-19], J[14-29], K[0-9] + // Sector: I[0-19], J[15-29], K[0-9] + // Intersection (clamped): I[0-19], J[15-29], K[0-9] + auto record = createEqualsRecord( "FIPNUM", 2, 1, 20, 15, 30, 1, 10 ); + + auto result = RicExportEclipseSectorModelFeature::processEqualsRecord( record, min, max, refinement ); + + ASSERT_TRUE( result.has_value() ); + + // Check transformed coordinates (sector-relative, 1-based) + // After clamping: I[0-19], J[15-29], K[0-9] (0-based) + // Transformed to sector-relative (min at 0,15,0): + // I: (0-0)*1+1 = 1 to (19-0)*1+1 = 20 + // J: (15-15)*1+1 = 1 to (29-15)*1+1 = 15 + // K: (0-0)*1+1 = 1 to (9-0)*1+1 = 10 + EXPECT_EQ( 1, result->getItem( 2 ).get( 0 ) ); // I1 + EXPECT_EQ( 20, result->getItem( 3 ).get( 0 ) ); // I2 + EXPECT_EQ( 1, result->getItem( 4 ).get( 0 ) ); // J1 + EXPECT_EQ( 15, result->getItem( 5 ).get( 0 ) ); // J2 + EXPECT_EQ( 1, result->getItem( 6 ).get( 0 ) ); // K1 + EXPECT_EQ( 10, result->getItem( 7 ).get( 0 ) ); // K2 + + // Check field name and value are preserved + EXPECT_EQ( "FIPNUM", result->getItem( 0 ).get( 0 ) ); + EXPECT_EQ( 2, result->getItem( 1 ).get( 0 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test EQUALS record completely inside sector (no clamping) +//-------------------------------------------------------------------------------------------------- +TEST( RicExportEclipseSectorModelFeature, ProcessEqualsRecord_CompletelyInside ) +{ + // Sector: min=(0, 0, 0), max=(19, 19, 9) (inclusive, cells [0-19, 0-19, 0-9]) + cvf::Vec3st min( 0, 0, 0 ); + cvf::Vec3st max( 19, 19, 9 ); + cvf::Vec3st refinement( 1, 1, 1 ); + + // EQUALS record completely inside: FIPNUM 3 5 15 5 15 2 8 (1-based Eclipse) + // Converts to 0-based: I[4-14], J[4-14], K[1-7] + auto record = createEqualsRecord( "FIPNUM", 3, 5, 15, 5, 15, 2, 8 ); + + auto result = RicExportEclipseSectorModelFeature::processEqualsRecord( record, min, max, refinement ); + + ASSERT_TRUE( result.has_value() ); + + // No clamping, direct transformation (sector min is 0,0,0) + // I: (4-0)*1+1 = 5 to (14-0)*1+1 = 15 + // J: (4-0)*1+1 = 5 to (14-0)*1+1 = 15 + // K: (1-0)*1+1 = 2 to (7-0)*1+1 = 8 + EXPECT_EQ( 5, result->getItem( 2 ).get( 0 ) ); // I1 + EXPECT_EQ( 15, result->getItem( 3 ).get( 0 ) ); // I2 + EXPECT_EQ( 5, result->getItem( 4 ).get( 0 ) ); // J1 + EXPECT_EQ( 15, result->getItem( 5 ).get( 0 ) ); // J2 + EXPECT_EQ( 2, result->getItem( 6 ).get( 0 ) ); // K1 + EXPECT_EQ( 8, result->getItem( 7 ).get( 0 ) ); // K2 +} + +//-------------------------------------------------------------------------------------------------- +/// Test EQUALS record completely outside sector +//-------------------------------------------------------------------------------------------------- +TEST( RicExportEclipseSectorModelFeature, ProcessEqualsRecord_CompletelyOutside ) +{ + // Sector: min=(0, 0, 0), max=(9, 9, 9) (inclusive, cells [0-9, 0-9, 0-9]) + cvf::Vec3st min( 0, 0, 0 ); + cvf::Vec3st max( 9, 9, 9 ); + cvf::Vec3st refinement( 1, 1, 1 ); + + // EQUALS record outside: FIPNUM 4 20 30 20 30 1 10 (1-based Eclipse) + // Converts to 0-based: I[19-29], J[19-29], K[0-9] + // No overlap with sector + auto record = createEqualsRecord( "FIPNUM", 4, 20, 30, 20, 30, 1, 10 ); + + auto result = RicExportEclipseSectorModelFeature::processEqualsRecord( record, min, max, refinement ); + + EXPECT_FALSE( result.has_value() ); + EXPECT_TRUE( result.error().contains( "does not overlap" ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test invalid EQUALS record (insufficient items) +//-------------------------------------------------------------------------------------------------- +TEST( RicExportEclipseSectorModelFeature, ProcessEqualsRecord_InvalidRecord ) +{ + cvf::Vec3st min( 0, 0, 0 ); + cvf::Vec3st max( 9, 9, 9 ); + cvf::Vec3st refinement( 1, 1, 1 ); + + // Create a record with only 5 items (insufficient) + std::vector items; + items.push_back( RifOpmDeckTools::item( "FIELD", std::string( "FIPNUM" ) ) ); + items.push_back( RifOpmDeckTools::item( "VALUE", 1 ) ); + items.push_back( RifOpmDeckTools::item( "I1", 1 ) ); + items.push_back( RifOpmDeckTools::item( "I2", 10 ) ); + items.push_back( RifOpmDeckTools::item( "J1", 1 ) ); + // Missing J2, K1, K2 + + Opm::DeckRecord record{ std::move( items ) }; + + auto result = RicExportEclipseSectorModelFeature::processEqualsRecord( record, min, max, refinement ); + + EXPECT_FALSE( result.has_value() ); + EXPECT_TRUE( result.error().contains( "insufficient items" ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test EQUALS record at sector boundary +//-------------------------------------------------------------------------------------------------- +TEST( RicExportEclipseSectorModelFeature, ProcessEqualsRecord_AtBoundary ) +{ + // Sector: min=(5, 5, 5), max=(14, 14, 14) (inclusive, cells [5-14, 5-14, 5-14]) + cvf::Vec3st min( 5, 5, 5 ); + cvf::Vec3st max( 14, 14, 14 ); + cvf::Vec3st refinement( 1, 1, 1 ); + + // EQUALS record exactly matching sector: 6 15 6 15 6 15 (1-based Eclipse) + // Converts to 0-based: I[5-14], J[5-14], K[5-14] + auto record = createEqualsRecord( "FIPNUM", 5, 6, 15, 6, 15, 6, 15 ); + + auto result = RicExportEclipseSectorModelFeature::processEqualsRecord( record, min, max, refinement ); + + ASSERT_TRUE( result.has_value() ); + + // Should span entire sector in all dimensions + // Transformed: (5-5)*1+1 = 1 to (14-5)*1+1 = 10 + EXPECT_EQ( 1, result->getItem( 2 ).get( 0 ) ); // I1 + EXPECT_EQ( 10, result->getItem( 3 ).get( 0 ) ); // I2 + EXPECT_EQ( 1, result->getItem( 4 ).get( 0 ) ); // J1 + EXPECT_EQ( 10, result->getItem( 5 ).get( 0 ) ); // J2 + EXPECT_EQ( 1, result->getItem( 6 ).get( 0 ) ); // K1 + EXPECT_EQ( 10, result->getItem( 7 ).get( 0 ) ); // K2 +}