Nested hybrid grid: Move well connections to reconstructed LGR cells

Well connections read from the restart file referenced the original refined
cells in the main grid. These cells are hidden after reconstruction, but the
well cell visualization does not check the invalid flag, so they were still
drawn. Re-point well connections and well heads to the corresponding LGR cells
and recompute the well cells per grid.
This commit is contained in:
Kristian Bendiksen committed 2026-10-15 13:21:55 +02:00
1 parent a789bdb4b4
commit bcfa6e9fea
3 files changed
+154 -1

No files matched your search

@@ -32,6 +32,10 @@
#include "RigNestedHybridGridLayout.h"
#include "RigNestedHybridGridResultTools.h"
#include "RigNncConnection.h"
#include "Well/RigSimWellData.h"
#include "Well/RigWellResultBranch.h"
#include "Well/RigWellResultFrame.h"
#include "Well/RigWellResultPoint.h"
#include "RiaDefines.h"
@@ -253,6 +257,7 @@ bool RigNestedHybridGridReconstructor::reconstruct( RigEclipseCaseData* caseData
updateActiveCellInfo( caseData, sourceCells );
extendFullLengthResults( caseData, origCellCount, sourceCells );
repointNncConnections( caseData, origCellCount, sourceCells );
repointWellResultPoints( caseData, origCellCount, sourceCells );
// Fill LGR cells of any already-loaded active-cell-indexed results from their source flat cells.
for ( auto model : { RiaDefines::PorosityModelType::MATRIX_MODEL, RiaDefines::PorosityModelType::FRACTURE_MODEL } )
@@ -496,3 +501,60 @@ void RigNestedHybridGridReconstructor::repointNncConnections( RigEclipseCaseData
}
}
}
//--------------------------------------------------------------------------------------------------
/// The well connections read from the restart file reference the original scattered refined cells
/// in the main grid. Those cells are hidden after reconstruction, but the well cell geometry does not
/// check the invalid flag, so the stale connections would show the hidden flat cells. Re-point each
/// such connection and well head to the corresponding LGR cell.
//--------------------------------------------------------------------------------------------------
void RigNestedHybridGridReconstructor::repointWellResultPoints( RigEclipseCaseData* caseData,
size_t origCellCount,
const std::map<size_t, size_t>& sourceCells )
{
RigMainGrid* grid = caseData->mainGrid();
if ( !grid || caseData->wellResults().empty() ) return;
std::vector<size_t> flatToLgr( origCellCount, cvf::UNDEFINED_SIZE_T );
for ( const auto& [global, flat] : sourceCells )
{
if ( flat < origCellCount ) flatToLgr[flat] = global;
}
auto repoint = [&]( RigWellResultPoint& point )
{
// The flat grid has no LGRs of its own, so all original connections are in the main grid
if ( !point.isCell() || point.gridIndex() != 0 || point.cellIndex() >= origCellCount ) return;
const size_t global = flatToLgr[point.cellIndex()];
if ( global == cvf::UNDEFINED_SIZE_T ) return;
const RigCell& lgrCell = grid->cell( global );
point.setGridIndex( lgrCell.hostGrid()->gridIndex() );
point.setGridCellIndex( lgrCell.gridLocalCellIndex() );
};
cvf::Collection<RigSimWellData> wells = caseData->wellResults();
for ( auto& well : wells )
{
for ( RigWellResultFrame& frame : well->m_wellCellsTimeSteps )
{
RigWellResultPoint wellHead = frame.wellHead();
repoint( wellHead );
frame.setWellHead( wellHead );
std::vector<RigWellResultBranch> branches = frame.wellResultBranches();
for ( RigWellResultBranch& branch : branches )
{
std::vector<RigWellResultPoint> points = branch.branchResultPoints();
for ( RigWellResultPoint& point : points )
repoint( point );
branch.setBranchResultPoints( points );
}
frame.setWellResultBranches( branches );
}
}
// Recomputes the well cells per grid for the new grid set
caseData->setSimWellData( wells );
}
@@ -93,4 +93,8 @@ private:
// Re-point existing (file) NNC connections that referenced the original scattered refined cells
// to the new LGR cells, preserving transmissibilities (connection results are indexed by position).
static void repointNncConnections( RigEclipseCaseData* caseData, size_t origCellCount, const std::map<size_t, size_t>& sourceCells );
// Re-point simulation well connections (and well heads) that referenced the original scattered
// refined cells to the new LGR cells, and recompute the well cells per grid.
static void repointWellResultPoints( RigEclipseCaseData* caseData, size_t origCellCount, const std::map<size_t, size_t>& sourceCells );
};
@@ -42,6 +42,11 @@
#include "RimCellRangeFilter.h"
#include "RimEclipseResultCase.h"
#include "Well/RigSimWellData.h"
#include "Well/RigWellResultBranch.h"
#include "Well/RigWellResultFrame.h"
#include "Well/RigWellResultPoint.h"
#include <QDir>
#include <QFile>
@@ -172,6 +177,38 @@ protected:
s_input = buildInput( dir );
// The restart file of this model has no well connections, so add a synthetic well with
// connections into a refined and an unrefined flat cell to check that they are re-pointed.
for ( size_t i = 0; i < s_input.refine.size(); i++ )
{
if ( s_input.refine[i] > 1 && s_wellRefinedFlatCell == cvf::UNDEFINED_SIZE_T ) s_wellRefinedFlatCell = i;
if ( s_input.refine[i] == 1 && s_wellUnrefinedFlatCell == cvf::UNDEFINED_SIZE_T ) s_wellUnrefinedFlatCell = i;
}
{
auto makePoint = []( size_t cellIndex )
{
RigWellResultPoint point;
point.setGridIndex( 0 );
point.setGridCellIndex( cellIndex );
return point;
};
RigWellResultBranch branch;
branch.setBranchResultPoints( { makePoint( s_wellRefinedFlatCell ), makePoint( s_wellUnrefinedFlatCell ) } );
RigWellResultFrame frame;
frame.setWellHead( makePoint( s_wellRefinedFlatCell ) );
frame.addWellResultBranch( branch );
cvf::ref<RigSimWellData> well = new RigSimWellData;
well->setWellName( "SYNTHETIC_NHG_WELL" );
well->m_wellCellsTimeSteps.push_back( frame );
cvf::Collection<RigSimWellData> wells = s_reservoir->wellResults();
wells.push_back( well.p() );
s_reservoir->setSimWellData( wells );
}
QString err;
if ( !RigNestedHybridGridReconstructor::reconstruct( s_reservoir.p(), s_input, &err ) )
{
@@ -204,6 +241,8 @@ protected:
static RigNestedHybridGridReconstructor::NestedHybridInput s_input;
static RigEclipseResultAddress s_staticAddr;
static size_t s_activeCellsBeforeReconstruct;
static size_t s_wellRefinedFlatCell;
static size_t s_wellUnrefinedFlatCell;
static bool s_setupOk;
static QString s_setupError;
};
@@ -214,7 +253,9 @@ cvf::ref<RifReaderEclipseOutput> RigNestedHybridGridReconstru
RigNestedHybridGridReconstructor::NestedHybridInput RigNestedHybridGridReconstructorTest::s_input;
RigEclipseResultAddress RigNestedHybridGridReconstructorTest::s_staticAddr;
size_t RigNestedHybridGridReconstructorTest::s_activeCellsBeforeReconstruct = 0;
bool RigNestedHybridGridReconstructorTest::s_setupOk = false;
size_t RigNestedHybridGridReconstructorTest::s_wellRefinedFlatCell = cvf::UNDEFINED_SIZE_T;
size_t RigNestedHybridGridReconstructorTest::s_wellUnrefinedFlatCell = cvf::UNDEFINED_SIZE_T;
bool RigNestedHybridGridReconstructorTest::s_setupOk = false;
QString RigNestedHybridGridReconstructorTest::s_setupError;
//--------------------------------------------------------------------------------------------------
@@ -950,3 +991,49 @@ TEST_F( RigNestedHybridGridReconstructorTest, RangeFilterUsesOriginalIjk )
EXPECT_GE( visibleLevels.size(), 2u ) << "axis " << slice.axis;
}
}
//--------------------------------------------------------------------------------------------------
/// Simulation well connections into refined flat cells follow the cells into the reconstructed
/// LGRs, so the hidden flat cells are not shown as well cells in the main grid. Connections into
/// unrefined cells stay in the main grid.
//--------------------------------------------------------------------------------------------------
TEST_F( RigNestedHybridGridReconstructorTest, WellConnectionsFollowLgrCells )
{
ASSERT_NE( s_wellRefinedFlatCell, cvf::UNDEFINED_SIZE_T );
ASSERT_NE( s_wellUnrefinedFlatCell, cvf::UNDEFINED_SIZE_T );
const RigSimWellData* well = nullptr;
for ( const auto& w : s_reservoir->wellResults() )
if ( w->m_wellName == "SYNTHETIC_NHG_WELL" ) well = w.p();
ASSERT_NE( well, nullptr );
ASSERT_EQ( well->m_wellCellsTimeSteps.size(), 1u );
const RigWellResultFrame& frame = well->m_wellCellsTimeSteps.front();
auto expectInLgrCellFrom = [&]( const RigWellResultPoint& point, size_t flatCell )
{
ASSERT_TRUE( point.isCell() );
ASSERT_GT( point.gridIndex(), 0u );
const RigGridBase* lgr = grid()->gridByIndex( point.gridIndex() );
ASSERT_LT( point.cellIndex(), lgr->cellCount() );
const auto& sourceCells = grid()->nestedHybridLgrSourceCells();
auto it = sourceCells.find( lgr->reservoirCellIndex( point.cellIndex() ) );
ASSERT_NE( it, sourceCells.end() );
EXPECT_EQ( it->second, flatCell );
};
expectInLgrCellFrom( frame.wellHead(), s_wellRefinedFlatCell );
const auto points = frame.allResultPoints();
ASSERT_EQ( points.size(), 2u );
expectInLgrCellFrom( points[0], s_wellRefinedFlatCell );
EXPECT_EQ( points[1].gridIndex(), 0u );
EXPECT_EQ( points[1].cellIndex(), s_wellUnrefinedFlatCell );
// The well cells used for visualization no longer include the hidden flat cell
const cvf::UByteArray* mainGridWellCells = s_reservoir->wellCellsInGrid( 0 );
ASSERT_NE( mainGridWellCells, nullptr );
EXPECT_FALSE( ( *mainGridWellCells )[s_wellRefinedFlatCell] );
EXPECT_TRUE( ( *mainGridWellCells )[s_wellUnrefinedFlatCell] );
}