Skip to content

Nested hybrid grid: import as a reconstructed LGR hierarchy#14231

Draft
kriben wants to merge 7 commits into
devfrom
nested-hybrid-grid-lgr
Draft

Nested hybrid grid: import as a reconstructed LGR hierarchy#14231
kriben wants to merge 7 commits into
devfrom
nested-hybrid-grid-lgr

Conversation

@kriben

@kriben kriben commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Add support for importing Eclipse "nested hybrid grids" (produced by xtgeo/fmu-tools). Such a grid is a single flat EGRID in which the refined region is appended to the end of the I axis at higher resolution and connected to the coarse grid through NNCs; in IJK space the refined cells are far from their coarse parent, but their COORD/ZCORN place them inside the coarse region. The coarse parent cells are collapsed to zero volume, so the parent of each refined cell is supplied explicitly by a HOSTNUM sidecar.

Both sidecars are auto-detected next to the grid file:

  • _NEST_ID.grdecl : per-cell nesting level, loaded as a discrete category property for coloring/filtering.
  • _HOSTNUM.grdecl : per refined cell, the 1-based natural index of its parent coarse cell (0 otherwise).

RigNestedHybridGridReconstructor rebuilds each refined region as a true RigLocalGrid: it appends contiguous cells/nodes, copies the real refined geometry, links each cell to its parent (subGrid/parentCellIndex), hides the original scattered cells, transfers active-cell result indexing, copies result values onto the LGR cells, and re-points the file NNCs to the LGR cells. The reconstructed LGR is flagged so it is excluded from the on-file grid count, preventing the result reader from reading it as a phantom grid.

The refined region then appears as a proper LGR with correct geometry, INDEX_I/J/K, static/dynamic results and computed results (e.g. SOIL). Normal grids (no sidecar present) are unaffected.

Key changes:

  • RigNestedHybridGridReconstructor (new) + RimEclipseResultCase hooks
  • RigLocalGrid isReconstructedGrid flag; RigMainGrid gridCountOnFile and nestedHybridLgrSourceCells
  • RigCaseCellResultsData: copy results onto reconstructed-LGR cells
  • RiaResultNames: NEST_ID name + category-result handling
  • RigActiveCellInfo::computeDerivedData made idempotent
  • Unit tests in RigNestedHybridGridReconstructor-Test.cpp

@kriben kriben force-pushed the nested-hybrid-grid-lgr branch 2 times, most recently from 912ee2c to 46fc505 Compare June 24, 2026 10:35
kriben added 6 commits June 24, 2026 12:36
Add support for importing Eclipse "nested hybrid grids" (produced by
xtgeo/fmu-tools). Such a grid is a single flat EGRID in which the refined
region is appended to the end of the I axis at higher resolution and
connected to the coarse grid through NNCs; in IJK space the refined cells are
far from their coarse parent, but their COORD/ZCORN place them inside the
coarse region. The coarse parent cells are collapsed to zero volume, so the
parent of each refined cell is supplied explicitly by a HOSTNUM sidecar.

Both sidecars are auto-detected next to the grid file:
- <base>_NEST_ID.grdecl : per-cell nesting level, loaded as a discrete
  category property for coloring/filtering.
- <base>_HOSTNUM.grdecl : per refined cell, the 1-based natural index of its
  parent coarse cell (0 otherwise).

RigNestedHybridGridReconstructor rebuilds each refined region as a true
RigLocalGrid: it appends contiguous cells/nodes, copies the real refined
geometry, links each cell to its parent (subGrid/parentCellIndex), hides the
original scattered cells, transfers active-cell result indexing, copies result
values onto the LGR cells, and re-points the file NNCs to the LGR cells. The
reconstructed LGR is flagged so it is excluded from the on-file grid count,
preventing the result reader from reading it as a phantom grid.

The refined region then appears as a proper LGR with correct geometry,
INDEX_I/J/K, static/dynamic results and computed results (e.g. SOIL). Normal
grids (no sidecar present) are unaffected.

Key changes:
- RigNestedHybridGridReconstructor (new) + RimEclipseResultCase hooks
- RigLocalGrid isReconstructedGrid flag; RigMainGrid gridCountOnFile and
  nestedHybridLgrSourceCells
- RigCaseCellResultsData: copy results onto reconstructed-LGR cells
- RiaResultNames: NEST_ID name + category-result handling
- RigActiveCellInfo::computeDerivedData made idempotent
- Unit tests in RigNestedHybridGridReconstructor-Test.cpp
…sidecars

Adapt the nested hybrid grid import to the current generator export format,
which no longer provides HOSTNUM. The refined region is described by sidecars
next to the flat EGRID: <base>_REFINE.grdecl (per-cell nesting level, replaces
NEST_ID) and <base>_OLDIJK.grdecl (OLDI/OLDJ/OLDK = the parent coarse cell IJK,
TMPI/TMPJ/TMPK = the cell's local refined coordinates).

The flat grid is kept as the main grid (result reading is untouched) and one
LGR is appended per refinement level: each level whose cells are a uniform
refinement of the coarse grid (verified via TMP/OLD) becomes a single
CARFIN-style RigLocalGrid over the coarse block, with missing cells left as
inactive holes. Levels that refine another level rather than the coarse grid
(level 4 inside level 3) are reconstructed as true LGR-in-LGR: their cells are
merged into connected regions in the refined-parent coordinate space (so a stack
of refined parent cells becomes a single LGR), one LGR per region, nested under
the level-3 cells it subdivides, with synthesized bounding-box geometry for the
collapsed host cells.

Also derive the parent-child mapping (each refined cell to its coarse parent)
and add a volume-weighted QC aggregate: computeNestedHybridCoarseAggregate
produces a <RESULT>_COARSE result holding, on every cell of a coarse parent, the
volume-weighted average of the source result over its refined cells.

Fix a hard crash when opening a project whose persisted grid set no longer
matches the regenerated LGRs: RimGridCollection::indicesToVisibleGrids now skips
grid indices beyond the current grid count.

Key changes:
- RiaResultNames: add refine() ("REFINE") and treat it as a category result
- RimEclipseResultCase: REFINE + OLDIJK sidecar detection and reconstruction trigger
- RigNestedHybridGridReconstructor: rewritten for multi-level, non-rectangular,
  nested reconstruction; new NestedHybridInput API
- RigMainGrid: store the per-cell coarse-parent mapping
- RigCaseCellResultsData: computeNestedHybridCoarseAggregate
- RimGridCollection: guard against stale persisted grid indices
- Unit tests rewritten for the new format and L4 nesting
Replace the fixed two-pass (primary level + one nesting layer) with a single
shallow-to-deep pass that supports nested refinement chains of any depth.

Every reconstructed cell is indexed by its TMP. Primary levels (direct uniform
refinements of the coarse grid) record all of their box cells, including holes,
so a deeper level can nest under a cell that was itself further refined and has
no geometry of its own. For each level, the cell's immediate parent is resolved
as the unique already-built cell one level shallower that shares its TMP; if
enough cells resolve, the level is nested (buildLocalGrid placed inside the
resolved parent grid, grouped by parent grid and merged into connected regions),
otherwise it is treated as a direct coarse refinement. Cells whose parent cannot
be resolved unambiguously are deferred (left as flat cells) rather than
mis-nested, so reconstruction never corrupts geometry.

How deep it reaches depends on the export's TMP encoding; the DROGON test case
still reconstructs identically (level 2, level 3, and level 4 merged into 12
nested regions). The OLD-based parent mapping and QC aggregate work at any depth
regardless.
Add computeNestedHybridPerLevelAggregate, which produces one result
"<RESULT>_COARSE_L<level>" per refinement level: on each level-N cell it stores
the volume-weighted average of the source result over the level-N cells of that
cell's immediate parent (from the reconstructed LGR hierarchy). All other cells
are left undefined so each level's result shows only that level.

Each cell's level is taken from the REFINE result (full-length, authoritative)
so cells of different refinement levels are never combined; the accumulation is
keyed by (level, parent) and falls back to the LGR name only if REFINE is not
loaded.

On load, these per-level aggregates are computed for PRESSURE and the
saturations alongside the existing collapse-to-coarse aggregate. A unit test
verifies the L4 averages against hand-computed values and that the result is
defined only on the level's own cells.
Reconstructed nested hybrid LGRs are laid out on a regular IJK box, but their
refinement can be non-conforming: two visible cells may be IJK neighbours
without geometrically sharing the face between them. The cell face visibility
filter hid such faces as if they were internal, leaving see-through gaps
("missing cell walls") on levels 2 and 3.

In RigGridCellFaceVisibilityFilter::isFaceVisible, when a shared face would be
hidden and the grid is a reconstructed LGR, draw the face unless the cell's face
corners actually coincide with the neighbour's opposite-face corners (within a
size-relative tolerance). Gated on RigLocalGrid::isReconstructedGrid(), so normal
grids and file-based LGRs are unaffected.
Add the nested hybrid grid test dataset (flat 150x84x96 EGRID plus the REFINE and
OLDIJK sidecars and INIT/UNRST results) used by the
RigNestedHybridGridReconstructor unit tests.
@kriben kriben force-pushed the nested-hybrid-grid-lgr branch from 46fc505 to f765609 Compare June 24, 2026 11:53
The flat nested hybrid grid piles the refined and collapsed coarse cells into the
same physical space, so a single findIntersectingCells query in a refined region
returns tens of thousands of overlapping cells. Running computeCachedData() (the
search tree, geometric fault detection and NNC computation) on that flat grid is
pathologically slow, and it was done before the LGR reconstruction and then again
afterwards.

Reconstruct the LGR hierarchy before computeCachedData() when the REFINE + OLDIJK
sidecars are present (loading input properties first so they are extended onto the
LGR cells), and compute the grid caches once, on the clean reconstructed grid.
Normal cases keep the previous order. Also drop the now-redundant computeCachedData
call inside reconstructNestedHybridGridIfPresent.

Because geometric fault detection now runs with the LGRs present, exclude
reconstructed-LGR cells from addUnNamedFaultFaces: their refinement boundaries are
not geological faults, and flagging them as such hid those cell faces during
rendering (the fault check precedes the face-conformance check in isFaceVisible).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant