From 282175bdb49da157290236af4fe28c5d8980b394 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 25 Mar 2026 12:50:22 +0100 Subject: [PATCH] #12957 Sector Model Export: add linear and logarithmic non-uniform refinement sub-modes Add NonUniformSubMode enum (Custom Widths, Linear Equal Split, Logarithmic Towards Center) to give users simpler alternatives to manually entering comma-separated fractional widths. Linear mode splits each cell in the range into N equal subcells. Logarithmic mode auto-computes widths with finer resolution near the center of the range using a geometric series. --- .../ExportCommands/RicExportSectorModelUi.cpp | 193 ++++++++++++++---- .../ExportCommands/RicExportSectorModelUi.h | 18 ++ .../RigGridExportAdapter.cpp | 46 ++--- .../ReservoirDataModel/RigGridExportAdapter.h | 11 +- .../RigNonUniformRefinement.cpp | 80 +++++++- .../RigNonUniformRefinement.h | 7 + .../RigNonUniformRefinement-Test.cpp | 121 +++++++++++ 7 files changed, 402 insertions(+), 74 deletions(-) diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp index 5c00e48bb5..a0d2fe8804 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp @@ -54,6 +54,14 @@ void AppEnum::setUp() addItem( RicExportSectorModelUi::NON_UNIFORM, "NON_UNIFORM", "Non-Uniform" ); setDefault( RicExportSectorModelUi::UNIFORM ); } +template <> +void AppEnum::setUp() +{ + addItem( RicExportSectorModelUi::CUSTOM_WIDTHS, "CUSTOM_WIDTHS", "Custom Widths" ); + addItem( RicExportSectorModelUi::LINEAR_EQUAL_SPLIT, "LINEAR_EQUAL_SPLIT", "Linear (Equal Split)" ); + addItem( RicExportSectorModelUi::LOGARITHMIC_CENTER, "LOGARITHMIC_CENTER", "Logarithmic (Towards Center)" ); + setDefault( RicExportSectorModelUi::CUSTOM_WIDTHS ); +} } // namespace caf CAF_PDM_SOURCE_INIT( RicExportSectorModelUi, "RicExportSectorModelUi" ); @@ -138,6 +146,22 @@ RicExportSectorModelUi::RicExportSectorModelUi() m_nonUniformRangeEndK.setMinValue( 1 ); CAF_PDM_InitField( &m_nonUniformIntervalsK, "NonUniformIntervalsK", QString( "0.5, 0.5" ), "Fractional Widths" ); + CAF_PDM_InitFieldNoDefault( &m_nonUniformSubMode, "NonUniformSubMode", "Non-Uniform Mode" ); + + CAF_PDM_InitField( &m_nonUniformSubcellCountI, "NonUniformSubcellCountI", 2, "Subcells per Cell" ); + m_nonUniformSubcellCountI.setRange( 2, 100 ); + CAF_PDM_InitField( &m_nonUniformSubcellCountJ, "NonUniformSubcellCountJ", 2, "Subcells per Cell" ); + m_nonUniformSubcellCountJ.setRange( 2, 100 ); + CAF_PDM_InitField( &m_nonUniformSubcellCountK, "NonUniformSubcellCountK", 2, "Subcells per Cell" ); + m_nonUniformSubcellCountK.setRange( 2, 100 ); + + CAF_PDM_InitField( &m_nonUniformTotalCellsI, "NonUniformTotalCellsI", 10, "Total Cells" ); + m_nonUniformTotalCellsI.setRange( 2, 1000 ); + CAF_PDM_InitField( &m_nonUniformTotalCellsJ, "NonUniformTotalCellsJ", 10, "Total Cells" ); + m_nonUniformTotalCellsJ.setRange( 2, 1000 ); + CAF_PDM_InitField( &m_nonUniformTotalCellsK, "NonUniformTotalCellsK", 10, "Total Cells" ); + m_nonUniformTotalCellsK.setRange( 2, 1000 ); + CAF_PDM_InitFieldNoDefault( &m_bcpropKeywords, "BcpropKeywords", "BCPROP Keywords" ); m_bcpropKeywords.uiCapability()->setUiEditorTypeName( caf::PdmUiTableViewEditor::uiEditorTypeName() ); m_bcpropKeywords.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN ); @@ -297,29 +321,69 @@ void RicExportSectorModelUi::defineUiOrdering( QString uiConfigName, caf::PdmUiO } else { - auto addDimensionGroup = [&]( const QString& label, + uiOrdering.add( &m_nonUniformSubMode ); + m_nonUniformSubMode.uiCapability()->setUiReadOnly( !isEnabled ); + uiOrdering.addNewLabel( "" ); + + auto subMode = m_nonUniformSubMode(); + + auto addDimensionGroup = [&]( const QString& label, caf::PdmField& enableField, caf::PdmField& rangeStartField, caf::PdmField& rangeEndField, - caf::PdmField& intervalsField ) + caf::PdmField& intervalsField, + caf::PdmField& subcellCountField, + caf::PdmField& totalCellsField ) { auto* grp = uiOrdering.addNewGroup( label ); grp->setCollapsedByDefault(); grp->add( &enableField ); grp->add( &rangeStartField ); grp->add( &rangeEndField ); - grp->add( &intervalsField ); + + if ( subMode == CUSTOM_WIDTHS ) + { + grp->add( &intervalsField ); + } + else if ( subMode == LINEAR_EQUAL_SPLIT ) + { + grp->add( &subcellCountField ); + } + else if ( subMode == LOGARITHMIC_CENTER ) + { + grp->add( &totalCellsField ); + } bool dimEnabled = isEnabled && enableField(); enableField.uiCapability()->setUiReadOnly( !isEnabled ); rangeStartField.uiCapability()->setUiReadOnly( !dimEnabled ); rangeEndField.uiCapability()->setUiReadOnly( !dimEnabled ); intervalsField.uiCapability()->setUiReadOnly( !dimEnabled ); + subcellCountField.uiCapability()->setUiReadOnly( !dimEnabled ); + totalCellsField.uiCapability()->setUiReadOnly( !dimEnabled ); }; - addDimensionGroup( "I Direction", m_nonUniformEnableI, m_nonUniformRangeStartI, m_nonUniformRangeEndI, m_nonUniformIntervalsI ); - addDimensionGroup( "J Direction", m_nonUniformEnableJ, m_nonUniformRangeStartJ, m_nonUniformRangeEndJ, m_nonUniformIntervalsJ ); - addDimensionGroup( "K Direction", m_nonUniformEnableK, m_nonUniformRangeStartK, m_nonUniformRangeEndK, m_nonUniformIntervalsK ); + addDimensionGroup( "I Direction", + m_nonUniformEnableI, + m_nonUniformRangeStartI, + m_nonUniformRangeEndI, + m_nonUniformIntervalsI, + m_nonUniformSubcellCountI, + m_nonUniformTotalCellsI ); + addDimensionGroup( "J Direction", + m_nonUniformEnableJ, + m_nonUniformRangeStartJ, + m_nonUniformRangeEndJ, + m_nonUniformIntervalsJ, + m_nonUniformSubcellCountJ, + m_nonUniformTotalCellsJ ); + addDimensionGroup( "K Direction", + m_nonUniformEnableK, + m_nonUniformRangeStartK, + m_nonUniformRangeEndK, + m_nonUniformIntervalsK, + m_nonUniformSubcellCountK, + m_nonUniformTotalCellsK ); } m_refinementMode.uiCapability()->setUiReadOnly( !isEnabled ); @@ -570,17 +634,37 @@ RigNonUniformRefinement RicExportSectorModelUi::nonUniformRefinement() const struct DimensionConfig { - bool enabled; - int rangeStart; - int rangeEnd; - QString intervals; + bool enabled; + int rangeStart; + int rangeEnd; + QString intervals; + int subcellCount; + int totalCells; RigNonUniformRefinement::Dimension dim; }; std::vector dims = { - { m_nonUniformEnableI(), m_nonUniformRangeStartI(), m_nonUniformRangeEndI(), m_nonUniformIntervalsI(), RigNonUniformRefinement::DimI }, - { m_nonUniformEnableJ(), m_nonUniformRangeStartJ(), m_nonUniformRangeEndJ(), m_nonUniformIntervalsJ(), RigNonUniformRefinement::DimJ }, - { m_nonUniformEnableK(), m_nonUniformRangeStartK(), m_nonUniformRangeEndK(), m_nonUniformIntervalsK(), RigNonUniformRefinement::DimK }, + { m_nonUniformEnableI(), + m_nonUniformRangeStartI(), + m_nonUniformRangeEndI(), + m_nonUniformIntervalsI(), + m_nonUniformSubcellCountI(), + m_nonUniformTotalCellsI(), + RigNonUniformRefinement::DimI }, + { m_nonUniformEnableJ(), + m_nonUniformRangeStartJ(), + m_nonUniformRangeEndJ(), + m_nonUniformIntervalsJ(), + m_nonUniformSubcellCountJ(), + m_nonUniformTotalCellsJ(), + RigNonUniformRefinement::DimJ }, + { m_nonUniformEnableK(), + m_nonUniformRangeStartK(), + m_nonUniformRangeEndK(), + m_nonUniformIntervalsK(), + m_nonUniformSubcellCountK(), + m_nonUniformTotalCellsK(), + RigNonUniformRefinement::DimK }, }; // Sector min indices (1-based) for converting original grid coordinates to sector-relative @@ -588,6 +672,8 @@ RigNonUniformRefinement RicExportSectorModelUi::nonUniformRefinement() const const QString dimLabels[3] = { "I", "J", "K" }; + auto subMode = m_nonUniformSubMode(); + RiaLogging::info( QString( "Non-uniform refinement: sector size [%1, %2, %3], sector min [%4, %5, %6]" ) .arg( sectorSize.x() ) .arg( sectorSize.y() ) @@ -600,32 +686,14 @@ RigNonUniformRefinement RicExportSectorModelUi::nonUniformRefinement() const { if ( !dc.enabled ) continue; - auto widths = parseWidths( dc.intervals ); - if ( widths.empty() ) - { - RiaLogging::warning( QString( "Non-uniform refinement %1: no valid widths parsed from '%2'" ) - .arg( dimLabels[static_cast( dc.dim )] ) - .arg( dc.intervals ) ); - continue; - } - // Convert from original grid coordinates (1-based) to sector-relative (0-based) int sectorStart = dc.rangeStart - sectorMin[static_cast( dc.dim )]; int sectorEnd = dc.rangeEnd - sectorMin[static_cast( dc.dim )]; - RiaLogging::info( QString( "Non-uniform refinement %1: grid range [%2, %3] -> sector range [%4, %5] (sector size %6, %7 widths)" ) - .arg( dimLabels[static_cast( dc.dim )] ) - .arg( dc.rangeStart ) - .arg( dc.rangeEnd ) - .arg( sectorStart ) - .arg( sectorEnd ) - .arg( result.sectorSize( dc.dim ) ) - .arg( widths.size() ) ); - // Clamp to sector bounds - int sectorMax = static_cast( result.sectorSize( dc.dim ) ) - 1; - sectorStart = std::max( 0, sectorStart ); - sectorEnd = std::min( sectorMax, sectorEnd ); + int sectorMaxIdx = static_cast( result.sectorSize( dc.dim ) ) - 1; + sectorStart = std::max( 0, sectorStart ); + sectorEnd = std::min( sectorMaxIdx, sectorEnd ); if ( sectorStart > sectorEnd ) { RiaLogging::warning( QString( "Non-uniform refinement %1: range [%2, %3] is outside sector after clamping, skipping" ) @@ -635,7 +703,57 @@ RigNonUniformRefinement RicExportSectorModelUi::nonUniformRefinement() const continue; } - result.distributeWidthsAcrossCells( dc.dim, sectorStart, sectorEnd, widths ); + if ( subMode == CUSTOM_WIDTHS ) + { + auto widths = parseWidths( dc.intervals ); + if ( widths.empty() ) + { + RiaLogging::warning( QString( "Non-uniform refinement %1: no valid widths parsed from '%2'" ) + .arg( dimLabels[static_cast( dc.dim )] ) + .arg( dc.intervals ) ); + continue; + } + + RiaLogging::info( + QString( "Non-uniform refinement %1: grid range [%2, %3] -> sector range [%4, %5] (sector size %6, %7 widths)" ) + .arg( dimLabels[static_cast( dc.dim )] ) + .arg( dc.rangeStart ) + .arg( dc.rangeEnd ) + .arg( sectorStart ) + .arg( sectorEnd ) + .arg( result.sectorSize( dc.dim ) ) + .arg( widths.size() ) ); + + result.distributeWidthsAcrossCells( dc.dim, sectorStart, sectorEnd, widths ); + } + else if ( subMode == LINEAR_EQUAL_SPLIT ) + { + // Each cell in the range gets exactly N equal subcells + auto equalFractions = RigNonUniformRefinement::generateEqualFractions( static_cast( dc.subcellCount ) ); + + for ( int c = sectorStart; c <= sectorEnd; ++c ) + { + result.setCumulativeFractions( dc.dim, static_cast( c ), equalFractions ); + } + + RiaLogging::info( QString( "Non-uniform refinement %1 (linear): %2 subcells per cell in range [%3, %4]" ) + .arg( dimLabels[static_cast( dc.dim )] ) + .arg( dc.subcellCount ) + .arg( sectorStart ) + .arg( sectorEnd ) ); + } + else if ( subMode == LOGARITHMIC_CENTER ) + { + auto widths = RigNonUniformRefinement::generateLogarithmicWidths( static_cast( dc.totalCells ) ); + + RiaLogging::info( QString( "Non-uniform refinement %1 (logarithmic): %2 total cells across range [%3, %4]" ) + .arg( dimLabels[static_cast( dc.dim )] ) + .arg( dc.totalCells ) + .arg( sectorStart ) + .arg( sectorEnd ) ); + + result.distributeWidthsAcrossCells( dc.dim, sectorStart, sectorEnd, widths ); + } RiaLogging::info( QString( "Non-uniform refinement %1: total refined count = %2 (was %3)" ) .arg( dimLabels[static_cast( dc.dim )] ) @@ -742,7 +860,8 @@ void RicExportSectorModelUi::fieldChangedByUi( const caf::PdmFieldHandle* change applyBoundaryDefaults(); } else if ( ( changedField == &m_boundaryCondition ) || ( changedField == &m_refineGrid ) || ( changedField == &m_enablePadding ) || - ( changedField == &m_nonUniformEnableI ) || ( changedField == &m_nonUniformEnableJ ) || ( changedField == &m_nonUniformEnableK ) ) + ( changedField == &m_nonUniformEnableI ) || ( changedField == &m_nonUniformEnableJ ) || + ( changedField == &m_nonUniformEnableK ) || ( changedField == &m_nonUniformSubMode ) ) { updateConnectedEditors(); } @@ -1021,7 +1140,7 @@ std::map RicExportSectorModelUi::validate( const QString& conf .arg( dv.sectorMin ) .arg( dv.sectorMax ); } - if ( parseWidths( dv.intervals() ).empty() ) + if ( m_nonUniformSubMode() == CUSTOM_WIDTHS && parseWidths( dv.intervals() ).empty() ) { fieldErrors[dv.intervals.keyword()] = QString( "%1 direction: Fractional widths must contain at least one positive value." ).arg( dv.label ); diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h index 6334e1dcab..a18d332a99 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h @@ -50,9 +50,17 @@ class RicExportSectorModelUi : public caf::PdmObject NON_UNIFORM }; + enum NonUniformSubMode + { + CUSTOM_WIDTHS, + LINEAR_EQUAL_SPLIT, + LOGARITHMIC_CENTER + }; + using GridBoxSelectionEnum = caf::AppEnum; using BoundaryConditionEnum = caf::AppEnum; using RefinementModeEnum = caf::AppEnum; + using NonUniformSubModeEnum = caf::AppEnum; public: RicExportSectorModelUi(); @@ -156,6 +164,16 @@ private: caf::PdmField m_nonUniformRangeEndK; caf::PdmField m_nonUniformIntervalsK; + caf::PdmField m_nonUniformSubMode; + + caf::PdmField m_nonUniformSubcellCountI; + caf::PdmField m_nonUniformSubcellCountJ; + caf::PdmField m_nonUniformSubcellCountK; + + caf::PdmField m_nonUniformTotalCellsI; + caf::PdmField m_nonUniformTotalCellsJ; + caf::PdmField m_nonUniformTotalCellsK; + caf::PdmField m_boundaryCondition; caf::PdmChildArrayField m_bcpropKeywords; caf::PdmField m_porvMultiplier; diff --git a/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.cpp b/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.cpp index 23ccf327e8..c64d802ca9 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.cpp @@ -228,32 +228,13 @@ std::array RigGridExportAdapter::getFaceCorners( size_t i, size_t applyCoordinateTransformation( originalCorners ); // Generate refined cell corners using the appropriate refinement algorithm - std::array refinedCorners; - if ( m_nonUniformRefinement.hasNonUniformRefinement() ) - { - size_t sectorI = mapping.originalI - m_min.x(); - size_t sectorJ = mapping.originalJ - m_min.y(); - size_t sectorK = mapping.originalK - m_min.z(); - - refinedCorners = - generateRefinedCellCornersNonUniform( originalCorners, - m_nonUniformRefinement.cumulativeFractions( RigNonUniformRefinement::DimI, sectorI ), - m_nonUniformRefinement.cumulativeFractions( RigNonUniformRefinement::DimJ, sectorJ ), - m_nonUniformRefinement.cumulativeFractions( RigNonUniformRefinement::DimK, sectorK ), - mapping.subI, - mapping.subJ, - mapping.subK ); - } - else - { - refinedCorners = generateRefinedCellCorners( originalCorners, - m_refinement.x(), - m_refinement.y(), - m_refinement.z(), - mapping.subI, - mapping.subJ, - mapping.subK ); - } + auto refinedCorners = computeRefinedCorners( originalCorners, + mapping.originalI, + mapping.originalJ, + mapping.originalK, + mapping.subI, + mapping.subJ, + mapping.subK ); // Extract the 4 corners for the requested face from the refined corners std::array faceCorners; @@ -389,7 +370,20 @@ std::array RigGridExportAdapter::getRefinedCellCorners( size_t origI, size_t origJ, size_t origK, size_t subI, size_t subJ, size_t subK ) const { std::array originalCorners = getOriginalCellCorners( origI, origJ, origK ); + return computeRefinedCorners( originalCorners, origI, origJ, origK, subI, subJ, subK ); +} +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::array RigGridExportAdapter::computeRefinedCorners( const std::array& originalCorners, + size_t origI, + size_t origJ, + size_t origK, + size_t subI, + size_t subJ, + size_t subK ) const +{ if ( m_nonUniformRefinement.hasNonUniformRefinement() ) { size_t sectorI = origI - m_min.x(); diff --git a/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h b/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h index 1d65964234..077e67a6cc 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h +++ b/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h @@ -118,8 +118,15 @@ private: // Internal methods to handle original vs refined cell access std::array getOriginalCellCorners( size_t origI, size_t origJ, size_t origK ) const; std::array getRefinedCellCorners( size_t origI, size_t origJ, size_t origK, size_t subI, size_t subJ, size_t subK ) const; - void applyCoordinateTransformation( std::array& corners ) const; - void applyCoordinateTransformation( std::array& corners ) const; + std::array computeRefinedCorners( const std::array& originalCorners, + size_t origI, + size_t origJ, + size_t origK, + size_t subI, + size_t subJ, + size_t subK ) const; + void applyCoordinateTransformation( std::array& corners ) const; + void applyCoordinateTransformation( std::array& corners ) const; // Helper to calculate original cell indices and subcell indices from refined indices struct CellMapping diff --git a/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.cpp b/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.cpp index 485272996f..3a8b14e1d6 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.cpp @@ -19,6 +19,7 @@ #include "RigNonUniformRefinement.h" #include +#include #include //-------------------------------------------------------------------------------------------------- @@ -185,8 +186,10 @@ void RigNonUniformRefinement::distributeWidthsAcrossCells( Dimension dim, size_t // Always add 1.0 as the final boundary (the cell end) localFractions.push_back( 1.0 ); - setCumulativeFractions( dim, startIndex + c, localFractions ); + m_fractions[dim][startIndex + c] = localFractions; } + + rebuildOffsets( dim ); } //-------------------------------------------------------------------------------------------------- @@ -201,6 +204,72 @@ bool RigNonUniformRefinement::hasNonUniformRefinement() const return false; } +//-------------------------------------------------------------------------------------------------- +/// Generate N equal cumulative fractions: {1/N, 2/N, ..., 1.0}. +//-------------------------------------------------------------------------------------------------- +std::vector RigNonUniformRefinement::generateEqualFractions( size_t subcellCount ) +{ + if ( subcellCount == 0 ) return { 1.0 }; + + std::vector fractions; + fractions.reserve( subcellCount ); + for ( size_t i = 1; i <= subcellCount; ++i ) + { + fractions.push_back( static_cast( i ) / static_cast( subcellCount ) ); + } + fractions.back() = 1.0; + return fractions; +} + +//-------------------------------------------------------------------------------------------------- +/// Generate logarithmic widths with finer resolution near the center. +/// Uses a geometric series with ratio ~2.0 for one half, then mirrors for symmetry. +/// Center cells get the smallest widths, edge cells the largest. +//-------------------------------------------------------------------------------------------------- +std::vector RigNonUniformRefinement::generateLogarithmicWidths( size_t totalCells ) +{ + if ( totalCells == 0 ) return {}; + if ( totalCells == 1 ) return { 1.0 }; + + const double ratio = 2.0; + + // Build widths for the first half (edge to center: large to small) + size_t halfCount = totalCells / 2; + + std::vector halfWidths; + halfWidths.reserve( halfCount ); + + for ( size_t i = 0; i < halfCount; ++i ) + { + // Largest at index 0 (edge), smallest at index halfCount-1 (center) + halfWidths.push_back( std::pow( ratio, static_cast( halfCount - 1 - i ) ) ); + } + + // Build full symmetric widths vector + std::vector widths; + widths.reserve( totalCells ); + + // First half: edge to center + for ( size_t i = 0; i < halfCount; ++i ) + { + widths.push_back( halfWidths[i] ); + } + + // If odd number of cells, add center cell with smallest width + if ( totalCells % 2 == 1 ) + { + widths.push_back( 1.0 ); + } + + // Second half: center to edge (mirror) + for ( size_t i = halfCount; i > 0; --i ) + { + widths.push_back( halfWidths[i - 1] ); + } + + return widths; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -214,14 +283,7 @@ RigNonUniformRefinement RigNonUniformRefinement::fromUniform( const cvf::Vec3st& { if ( refCount[dim] <= 1 ) continue; - // Create uniform cumulative fractions - std::vector uniformFractions; - uniformFractions.reserve( refCount[dim] ); - for ( size_t i = 1; i <= refCount[dim]; ++i ) - { - uniformFractions.push_back( static_cast( i ) / static_cast( refCount[dim] ) ); - } - uniformFractions.back() = 1.0; + auto uniformFractions = generateEqualFractions( refCount[dim] ); for ( size_t idx = 0; idx < result.m_sectorSize[dim]; ++idx ) { diff --git a/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.h b/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.h index 9331ad1ce1..d707197b0d 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.h +++ b/ApplicationLibCode/ReservoirDataModel/RigNonUniformRefinement.h @@ -76,6 +76,13 @@ public: // Each cell in the range occupies an equal share (1/numCells) of the global range. void distributeWidthsAcrossCells( Dimension dim, size_t startIndex, size_t endIndex, const std::vector& widths ); + // Generate N equal cumulative fractions, suitable for splitting a single cell into N equal subcells. + static std::vector generateEqualFractions( size_t subcellCount ); + + // Generate logarithmic widths with finer resolution near the center of the range. + // Uses a geometric series (ratio ~2.0), mirrors for symmetry. Not normalized. + static std::vector generateLogarithmicWidths( size_t totalCells ); + // Create a non-uniform refinement that is equivalent to uniform refinement static RigNonUniformRefinement fromUniform( const cvf::Vec3st& refinement, const cvf::Vec3st& sectorSize ); diff --git a/ApplicationLibCode/UnitTests/RigNonUniformRefinement-Test.cpp b/ApplicationLibCode/UnitTests/RigNonUniformRefinement-Test.cpp index 2cff83304d..b491aec997 100644 --- a/ApplicationLibCode/UnitTests/RigNonUniformRefinement-Test.cpp +++ b/ApplicationLibCode/UnitTests/RigNonUniformRefinement-Test.cpp @@ -274,6 +274,127 @@ TEST( RigNonUniformRefinement, DistributeConcentratedWidths ) EXPECT_EQ( nuRef.totalRefinedCount( RigNonUniformRefinement::DimI ), 5u ); } +//-------------------------------------------------------------------------------------------------- +/// Test generateLogarithmicWidths: even count, verify symmetry and center < edge +//-------------------------------------------------------------------------------------------------- +TEST( RigNonUniformRefinement, GenerateLogarithmicWidthsSymmetry ) +{ + auto widths = RigNonUniformRefinement::generateLogarithmicWidths( 6 ); + ASSERT_EQ( widths.size(), 6u ); + + // Verify symmetry: widths[i] == widths[n-1-i] + for ( size_t i = 0; i < widths.size() / 2; ++i ) + { + EXPECT_DOUBLE_EQ( widths[i], widths[widths.size() - 1 - i] ); + } + + // Center cells should be smaller than edge cells + EXPECT_LT( widths[2], widths[0] ); + EXPECT_LT( widths[3], widths[0] ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test generateLogarithmicWidths: odd count, verify symmetry around center +//-------------------------------------------------------------------------------------------------- +TEST( RigNonUniformRefinement, GenerateLogarithmicWidthsOdd ) +{ + auto widths = RigNonUniformRefinement::generateLogarithmicWidths( 7 ); + ASSERT_EQ( widths.size(), 7u ); + + // Verify symmetry: widths[i] == widths[n-1-i] + for ( size_t i = 0; i < widths.size() / 2; ++i ) + { + EXPECT_DOUBLE_EQ( widths[i], widths[widths.size() - 1 - i] ); + } + + // Center cell (index 3) should be the smallest + for ( size_t i = 0; i < widths.size(); ++i ) + { + if ( i != 3 ) + { + EXPECT_LE( widths[3], widths[i] ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// Test generateLogarithmicWidths: single cell edge case +//-------------------------------------------------------------------------------------------------- +TEST( RigNonUniformRefinement, GenerateLogarithmicWidthsSingleCell ) +{ + auto widths = RigNonUniformRefinement::generateLogarithmicWidths( 1 ); + ASSERT_EQ( widths.size(), 1u ); + EXPECT_DOUBLE_EQ( widths[0], 1.0 ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test generateEqualFractions: produces correct cumulative fractions +//-------------------------------------------------------------------------------------------------- +TEST( RigNonUniformRefinement, GenerateEqualFractions ) +{ + auto fracs1 = RigNonUniformRefinement::generateEqualFractions( 1 ); + ASSERT_EQ( fracs1.size(), 1u ); + EXPECT_DOUBLE_EQ( fracs1[0], 1.0 ); + + auto fracs4 = RigNonUniformRefinement::generateEqualFractions( 4 ); + ASSERT_EQ( fracs4.size(), 4u ); + EXPECT_NEAR( fracs4[0], 0.25, 1e-10 ); + EXPECT_NEAR( fracs4[1], 0.50, 1e-10 ); + EXPECT_NEAR( fracs4[2], 0.75, 1e-10 ); + EXPECT_DOUBLE_EQ( fracs4[3], 1.0 ); + + // Edge case: 0 returns {1.0} + auto fracs0 = RigNonUniformRefinement::generateEqualFractions( 0 ); + ASSERT_EQ( fracs0.size(), 1u ); + EXPECT_DOUBLE_EQ( fracs0[0], 1.0 ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test linear equal split: each cell gets exactly N equal subcells +//-------------------------------------------------------------------------------------------------- +TEST( RigNonUniformRefinement, LinearEqualSplitPattern ) +{ + RigNonUniformRefinement nuRef( cvf::Vec3st( 5, 1, 1 ) ); + + // Apply N=3 equal subcells to cells 1-3 using generateEqualFractions + auto equalFractions = RigNonUniformRefinement::generateEqualFractions( 3 ); + + for ( size_t c = 1; c <= 3; ++c ) + { + nuRef.setCumulativeFractions( RigNonUniformRefinement::DimI, c, equalFractions ); + } + + // Cells outside range should be unaffected + EXPECT_EQ( nuRef.subcellCount( RigNonUniformRefinement::DimI, 0 ), 1u ); + EXPECT_EQ( nuRef.subcellCount( RigNonUniformRefinement::DimI, 4 ), 1u ); + + // Cells in range should each have exactly 3 subcells + EXPECT_EQ( nuRef.subcellCount( RigNonUniformRefinement::DimI, 1 ), 3u ); + EXPECT_EQ( nuRef.subcellCount( RigNonUniformRefinement::DimI, 2 ), 3u ); + EXPECT_EQ( nuRef.subcellCount( RigNonUniformRefinement::DimI, 3 ), 3u ); + + // Total: 1 + 3 + 3 + 3 + 1 = 11 + EXPECT_EQ( nuRef.totalRefinedCount( RigNonUniformRefinement::DimI ), 11u ); +} + +//-------------------------------------------------------------------------------------------------- +/// Test logarithmic distribution through distributeWidthsAcrossCells +//-------------------------------------------------------------------------------------------------- +TEST( RigNonUniformRefinement, LogarithmicDistribution ) +{ + RigNonUniformRefinement nuRef( cvf::Vec3st( 3, 1, 1 ) ); + + auto widths = RigNonUniformRefinement::generateLogarithmicWidths( 10 ); + ASSERT_EQ( widths.size(), 10u ); + + nuRef.distributeWidthsAcrossCells( RigNonUniformRefinement::DimI, 0, 2, widths ); + + // Total refined count should be >= number of widths (each width creates at least a boundary) + size_t totalRefined = nuRef.totalRefinedCount( RigNonUniformRefinement::DimI ); + EXPECT_GE( totalRefined, 3u ); // At minimum 1 per cell + EXPECT_TRUE( nuRef.hasNonUniformRefinement() ); +} + //-------------------------------------------------------------------------------------------------- /// Test non-uniform geometry: equal fractions should match uniform createHexCornerCoords //--------------------------------------------------------------------------------------------------