mirror of
https://github.com/OPM/ResInsight.git
synced 2026-08-27 05:37:21 -05:00
582 lines
28 KiB
C++
582 lines
28 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2026- 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 <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RigNestedHybridGridResultTools.h"
|
|
|
|
#include "RiaDefines.h"
|
|
#include "RiaLogging.h"
|
|
#include "RiaResultNames.h"
|
|
|
|
#include "RifEclipseKeywordContent.h"
|
|
#include "RifEclipseTextFileReader.h"
|
|
#include "RifInputPropertyLoader.h"
|
|
|
|
#include "RigActiveCellInfo.h"
|
|
#include "RigCaseCellResultsData.h"
|
|
#include "RigCell.h"
|
|
#include "RigEclipseCaseData.h"
|
|
#include "RigLocalGrid.h"
|
|
#include "RigMainGrid.h"
|
|
#include "RigNestedHybridGridReconstructor.h"
|
|
#include "RigTypeSafeIndex.h"
|
|
|
|
#include "RimEclipseInputProperty.h"
|
|
#include "RimEclipseInputPropertyCollection.h"
|
|
|
|
#include "cvfObject.h"
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Nested hybrid grid: the grid is a single flat EGRID, and the per-cell nesting level is provided
|
|
/// in a sidecar GRDECL file named "<grid-basename>_REFINE.grdecl" next to the grid file.
|
|
/// Returns the path to that sidecar if it exists, otherwise an empty string.
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RigNestedHybridGridResultTools::refineSidecarFilePath( const QString& gridFileName )
|
|
{
|
|
QFileInfo gridFileInfo( gridFileName );
|
|
if ( !gridFileInfo.exists() ) return {};
|
|
|
|
QDir dir = gridFileInfo.absoluteDir();
|
|
const QString baseName = gridFileInfo.completeBaseName();
|
|
|
|
// Filename convention, e.g. DROGON_NESTED.EGRID -> DROGON_NESTED_REFINE.grdecl
|
|
const QString suffix = "_" + RiaResultNames::refine();
|
|
const QStringList candidates = { baseName + suffix + ".grdecl", baseName + suffix + ".GRDECL" };
|
|
for ( const QString& candidate : candidates )
|
|
{
|
|
QString path = dir.absoluteFilePath( candidate );
|
|
if ( QFile::exists( path ) ) return path;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Nested hybrid grid: the parent mapping is provided in a sidecar GRDECL file named
|
|
/// "<grid-basename>_OLDIJK.grdecl" next to the grid file. It holds, per flat cell, the original
|
|
/// coarse cell IJK (OLDI/OLDJ/OLDK) and the local refined coordinates (TMPI/TMPJ/TMPK).
|
|
/// Returns its path if it exists.
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RigNestedHybridGridResultTools::oldIjkSidecarFilePath( const QString& gridFileName )
|
|
{
|
|
QFileInfo gridFileInfo( gridFileName );
|
|
if ( !gridFileInfo.exists() ) return {};
|
|
|
|
QDir dir = gridFileInfo.absoluteDir();
|
|
const QString baseName = gridFileInfo.completeBaseName();
|
|
|
|
const QStringList candidates = { baseName + "_OLDIJK.grdecl", baseName + "_OLDIJK.GRDECL" };
|
|
for ( const QString& candidate : candidates )
|
|
{
|
|
QString path = dir.absoluteFilePath( candidate );
|
|
if ( QFile::exists( path ) ) return path;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RigNestedHybridGridResultTools::importRefineSidecarIfPresent( const QString& gridFileName,
|
|
RimEclipseInputPropertyCollection* inputPropertyCollection,
|
|
RigEclipseCaseData* eclipseCaseData )
|
|
{
|
|
if ( !inputPropertyCollection || !eclipseCaseData ) return;
|
|
|
|
// Skip if the REFINE property is already loaded (e.g. restored from a saved project file)
|
|
for ( const RimEclipseInputProperty* prop : inputPropertyCollection->items() )
|
|
{
|
|
if ( prop->resultName() == RiaResultNames::refine() ) return;
|
|
}
|
|
|
|
const QString sidecarPath = refineSidecarFilePath( gridFileName );
|
|
if ( sidecarPath.isEmpty() ) return;
|
|
|
|
RiaLogging::info( QString( "Nested hybrid grid: loading REFINE property from %1" ).arg( sidecarPath ).toStdString() );
|
|
|
|
RifInputPropertyLoader::loadAndSynchronizeInputProperties( inputPropertyCollection, eclipseCaseData, std::vector<QString>{ sidecarPath }, false );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Load the OLDIJK sidecar (OLDI/OLDJ/OLDK/TMPI/TMPJ/TMPK) as input properties so the parent-cell
|
|
/// mapping is visible and scriptable, mirroring the REFINE property.
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RigNestedHybridGridResultTools::importOldIjkSidecarIfPresent( const QString& gridFileName,
|
|
RimEclipseInputPropertyCollection* inputPropertyCollection,
|
|
RigEclipseCaseData* eclipseCaseData )
|
|
{
|
|
if ( !inputPropertyCollection || !eclipseCaseData ) return;
|
|
|
|
// Skip if the OLDIJK properties are already loaded (e.g. restored from a saved project file)
|
|
for ( const RimEclipseInputProperty* prop : inputPropertyCollection->items() )
|
|
{
|
|
if ( prop->resultName().compare( "OLDI", Qt::CaseInsensitive ) == 0 ) return;
|
|
}
|
|
|
|
const QString sidecarPath = oldIjkSidecarFilePath( gridFileName );
|
|
if ( sidecarPath.isEmpty() ) return;
|
|
|
|
RiaLogging::info( QString( "Nested hybrid grid: loading OLDIJK properties from %1" ).arg( sidecarPath ).toStdString() );
|
|
|
|
RifInputPropertyLoader::loadAndSynchronizeInputProperties( inputPropertyCollection, eclipseCaseData, std::vector<QString>{ sidecarPath }, false );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RigNestedHybridGridResultTools::reconstructNestedHybridGridIfPresent( const QString& gridFileName, RigEclipseCaseData* eclipseCaseData )
|
|
{
|
|
if ( !eclipseCaseData || !eclipseCaseData->mainGrid() ) return;
|
|
|
|
const QString refinePath = refineSidecarFilePath( gridFileName );
|
|
const QString oldIjkPath = oldIjkSidecarFilePath( gridFileName );
|
|
if ( refinePath.isEmpty() || oldIjkPath.isEmpty() ) return;
|
|
|
|
// Read a single named integer keyword (rounded from the file's float values) from parsed content.
|
|
auto readIntKeyword = []( const std::vector<RifEclipseKeywordContent>& content, const QString& keyword )
|
|
{
|
|
std::vector<int> result;
|
|
for ( const auto& kw : content )
|
|
{
|
|
if ( QString::fromStdString( kw.keyword ).compare( keyword, Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
result.reserve( kw.values.size() );
|
|
for ( float v : kw.values )
|
|
result.push_back( static_cast<int>( std::lround( v ) ) );
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
auto refineContent = RifEclipseTextFileReader::readKeywordAndValues( refinePath.toStdString() );
|
|
auto oldIjkContent = RifEclipseTextFileReader::readKeywordAndValues( oldIjkPath.toStdString() );
|
|
|
|
RigNestedHybridGridReconstructor::NestedHybridInput input;
|
|
input.refine = readIntKeyword( refineContent, RiaResultNames::refine() );
|
|
input.oldI = readIntKeyword( oldIjkContent, "OLDI" );
|
|
input.oldJ = readIntKeyword( oldIjkContent, "OLDJ" );
|
|
input.oldK = readIntKeyword( oldIjkContent, "OLDK" );
|
|
input.tmpI = readIntKeyword( oldIjkContent, "TMPI" );
|
|
input.tmpJ = readIntKeyword( oldIjkContent, "TMPJ" );
|
|
input.tmpK = readIntKeyword( oldIjkContent, "TMPK" );
|
|
|
|
QString errorMessage;
|
|
RigNestedHybridGridReconstructor::reconstruct( eclipseCaseData, input, &errorMessage );
|
|
|
|
// The caller computes grid caches (search tree, faults, NNCs) once, after this reconstruction, so
|
|
// that the expensive geometric passes run on the clean grid rather than the flat overlapping one.
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RigNestedHybridGridResultTools::extendLgrResults( RigCaseCellResultsData* cellResults )
|
|
{
|
|
if ( !cellResults ) return;
|
|
|
|
RigMainGrid* mainGrid = cellResults->mainGrid();
|
|
RigActiveCellInfo* activeCellInfo = cellResults->activeCellInfo();
|
|
if ( !mainGrid || !activeCellInfo || mainGrid->nestedHybridLgrSourceCells().empty() ) return;
|
|
|
|
const size_t activeCellCount = activeCellInfo->reservoirActiveCellCount();
|
|
|
|
for ( const RigEclipseResultAddress& addr : cellResults->existingResults() )
|
|
{
|
|
std::vector<std::vector<double>>* timesteps = cellResults->modifiableCellScalarResultTimesteps( addr );
|
|
if ( !timesteps ) continue;
|
|
|
|
for ( std::vector<double>& values : *timesteps )
|
|
{
|
|
// Only active-cell-indexed arrays (length below the active-cell count). Full-length
|
|
// (all-cells) arrays are handled separately by the reconstructor.
|
|
if ( !values.empty() && values.size() < activeCellCount )
|
|
{
|
|
assignValuesToLgrs( cellResults, values );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Aggregate a source result onto each refined cell's parent COARSE cell - the pore-volume-weighted
|
|
/// average for intensive quantities (falling back to the bulk cell volume as weight if PORV is not
|
|
/// available), or the sum for extensive quantities (e.g. FIP) - then broadcast that aggregate back
|
|
/// onto every (active) cell of the parent - both the original flat refined cells and the
|
|
/// reconstructed LGR cells. Unrefined cells keep their own value. The result is stored as a
|
|
/// GENERATED result named "<sourceName>_COARSE" for all time steps.
|
|
//--------------------------------------------------------------------------------------------------
|
|
RigEclipseResultAddress RigNestedHybridGridResultTools::computeCoarseAggregate( RigCaseCellResultsData* cellResults,
|
|
const RigEclipseResultAddress& sourceAddress,
|
|
AggregationMode mode )
|
|
{
|
|
RigEclipseResultAddress invalid;
|
|
if ( !cellResults ) return invalid;
|
|
|
|
RigMainGrid* mainGrid = cellResults->mainGrid();
|
|
RigActiveCellInfo* activeCellInfo = cellResults->activeCellInfo();
|
|
if ( !mainGrid || !activeCellInfo ) return invalid;
|
|
|
|
const std::map<size_t, size_t>& coarseParents = mainGrid->nestedHybridCoarseParents();
|
|
const std::map<size_t, size_t>& sourceCells = mainGrid->nestedHybridLgrSourceCells();
|
|
if ( coarseParents.empty() ) return invalid;
|
|
|
|
if ( !cellResults->ensureKnownResultLoaded( sourceAddress ) ) return invalid;
|
|
|
|
// Cell volumes (active-cell indexed): the zero-volume mask that excludes the hidden flat
|
|
// duplicates, and the fallback weight if PORV is not available.
|
|
cellResults->computeCellVolumes();
|
|
RigEclipseResultAddress volAddr( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::riCellVolumeResultName() );
|
|
if ( !cellResults->ensureKnownResultLoaded( volAddr ) ) return invalid;
|
|
|
|
const size_t activeCellCount = activeCellInfo->reservoirActiveCellCount();
|
|
|
|
const size_t tsCount = cellResults->cellScalarResults( sourceAddress ).size();
|
|
if ( tsCount == 0 ) return invalid;
|
|
|
|
// Create the output result (GENERATED so the file reader never tries to read it). createResultEntry()
|
|
// pushes onto the backing storage and may reallocate it, invalidating any reference/pointer into the
|
|
// backing storage; bind volumes/sourceTs/porv only afterwards.
|
|
const QString outName = sourceAddress.resultName() + "_COARSE";
|
|
RigEclipseResultAddress outAddr( RiaDefines::ResultCatType::GENERATED, outName );
|
|
if ( !cellResults->hasResultEntry( outAddr ) ) cellResults->createResultEntry( outAddr, true );
|
|
std::vector<std::vector<double>>* outTs = cellResults->modifiableCellScalarResultTimesteps( outAddr );
|
|
if ( !outTs ) return invalid;
|
|
outTs->resize( tsCount );
|
|
|
|
const std::vector<double>& volumes = cellResults->cellScalarResults( volAddr, 0 );
|
|
const std::vector<std::vector<double>>& sourceTs = cellResults->cellScalarResults( sourceAddress );
|
|
|
|
// Pore volume (active-cell indexed) is the weight for the average; null if PORV is unavailable.
|
|
std::vector<double> porvTemp;
|
|
const std::vector<double>* porv = nullptr;
|
|
if ( mode == AggregationMode::PORE_VOLUME_WEIGHTED_AVERAGE )
|
|
{
|
|
porv = RigCaseCellResultsData::getResultIndexableStaticResult( activeCellInfo, cellResults, RiaResultNames::porv(), porvTemp );
|
|
}
|
|
|
|
auto activeIndex = [&]( size_t reservoirCell ) { return activeCellInfo->cellResultIndex( ReservoirCellIndex( reservoirCell ) ).value(); };
|
|
|
|
// The original flat refined cells of an L2/L3 region are hidden (zero volume) once moved into an
|
|
// LGR, so the real geometry/value lives on the LGR cell. Map each flat cell to the cell that
|
|
// carries its geometry: its LGR copy if it has one, otherwise the flat cell itself (e.g. cells
|
|
// that were left un-nested).
|
|
std::map<size_t, size_t> flatToGeometryCell;
|
|
for ( const auto& [lgrCell, flatCell] : sourceCells )
|
|
flatToGeometryCell[flatCell] = lgrCell;
|
|
auto geometryCell = [&]( size_t flatCell )
|
|
{
|
|
auto it = flatToGeometryCell.find( flatCell );
|
|
return it != flatToGeometryCell.end() ? it->second : flatCell;
|
|
};
|
|
|
|
for ( size_t ts = 0; ts < tsCount; ts++ )
|
|
{
|
|
const std::vector<double>& src = sourceTs[ts];
|
|
std::vector<double>& out = ( *outTs )[ts];
|
|
out = src; // unrefined cells keep their own value
|
|
if ( out.size() < activeCellCount ) out.resize( activeCellCount, HUGE_VAL );
|
|
|
|
// Accumulate per coarse parent, using the geometry-bearing cell. The zero-bulk-volume filter
|
|
// excludes the hidden flat duplicates in both modes (their PORV is a duplicate too), so no
|
|
// cell is counted twice.
|
|
std::map<size_t, std::pair<double, double>> acc; // parent -> (sum value[*weight], sum weight / count)
|
|
for ( const auto& [flatCell, parent] : coarseParents )
|
|
{
|
|
size_t ri = activeIndex( geometryCell( flatCell ) );
|
|
if ( ri == cvf::UNDEFINED_SIZE_T || ri >= src.size() || ri >= volumes.size() ) continue;
|
|
double v = src[ri];
|
|
if ( volumes[ri] <= 0.0 || v == HUGE_VAL ) continue;
|
|
if ( mode == AggregationMode::SUM )
|
|
{
|
|
auto& a = acc[parent];
|
|
a.first += v;
|
|
a.second += 1.0;
|
|
}
|
|
else
|
|
{
|
|
double w = ( porv && ri < porv->size() ) ? ( *porv )[ri] : volumes[ri];
|
|
if ( w <= 0.0 || w == HUGE_VAL ) continue;
|
|
auto& a = acc[parent];
|
|
a.first += v * w;
|
|
a.second += w;
|
|
}
|
|
}
|
|
|
|
auto aggregate = [&]( size_t parent, double fallback )
|
|
{
|
|
auto it = acc.find( parent );
|
|
if ( it != acc.end() && it->second.second > 0.0 )
|
|
return ( mode == AggregationMode::SUM ) ? it->second.first : it->second.first / it->second.second;
|
|
return fallback;
|
|
};
|
|
|
|
// Broadcast the parent aggregate onto every (active) cell of the parent - both the flat
|
|
// refined cell and its LGR copy - so the aggregate reads correctly on either representation.
|
|
for ( const auto& [flatCell, parent] : coarseParents )
|
|
{
|
|
const size_t gi = activeIndex( geometryCell( flatCell ) );
|
|
const double fallback = ( gi != cvf::UNDEFINED_SIZE_T && gi < src.size() ) ? src[gi] : HUGE_VAL;
|
|
const double value = aggregate( parent, fallback );
|
|
|
|
for ( size_t cell : { flatCell, geometryCell( flatCell ) } )
|
|
{
|
|
size_t ri = activeIndex( cell );
|
|
if ( ri != cvf::UNDEFINED_SIZE_T && ri < out.size() ) out[ri] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return outAddr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Per refinement level, compute the aggregate (pore-volume-weighted average or sum) of a source
|
|
/// result over the cells of each immediate parent and broadcast it back onto that level's cells. All
|
|
/// other cells are left undefined (blank) so each level's result shows only that level. One result
|
|
/// "<sourceName>_COARSE_L<level>" is created per level present (stored on the active refined cells;
|
|
/// the parent cells are inactive).
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<RigEclipseResultAddress> RigNestedHybridGridResultTools::computePerLevelAggregate( RigCaseCellResultsData* cellResults,
|
|
const RigEclipseResultAddress& sourceAddress,
|
|
AggregationMode mode )
|
|
{
|
|
std::vector<RigEclipseResultAddress> created;
|
|
if ( !cellResults ) return created;
|
|
|
|
RigMainGrid* mainGrid = cellResults->mainGrid();
|
|
RigActiveCellInfo* activeCellInfo = cellResults->activeCellInfo();
|
|
if ( !mainGrid || !activeCellInfo ) return created;
|
|
if ( mainGrid->nestedHybridLgrSourceCells().empty() ) return created; // not a reconstructed nested hybrid grid
|
|
|
|
if ( !cellResults->ensureKnownResultLoaded( sourceAddress ) ) return created;
|
|
|
|
cellResults->computeCellVolumes();
|
|
RigEclipseResultAddress volAddr( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::riCellVolumeResultName() );
|
|
if ( !cellResults->ensureKnownResultLoaded( volAddr ) ) return created;
|
|
|
|
const size_t activeCellCount = activeCellInfo->reservoirActiveCellCount();
|
|
const size_t totalCellCount = mainGrid->totalCellCount();
|
|
|
|
// Each cell's refinement level. Prefer the REFINE result (authoritative, full-length per cell) so
|
|
// cells of different levels are never combined; fall back to the LGR name only if REFINE is absent.
|
|
RigEclipseResultAddress refineAddr( RiaDefines::ResultCatType::INPUT_PROPERTY,
|
|
RiaDefines::ResultDataType::INTEGER,
|
|
RiaResultNames::refine() );
|
|
const std::vector<double>* refine = nullptr;
|
|
if ( cellResults->ensureKnownResultLoaded( refineAddr ) )
|
|
{
|
|
const std::vector<std::vector<double>>& ts = cellResults->cellScalarResults( refineAddr );
|
|
if ( !ts.empty() && ts[0].size() == totalCellCount ) refine = &ts[0];
|
|
}
|
|
|
|
auto levelFromName = []( const std::string& name )
|
|
{
|
|
const std::string prefix = "LGR_NHG_L";
|
|
if ( name.rfind( prefix, 0 ) != 0 ) return -1;
|
|
int value = 0;
|
|
bool any = false;
|
|
for ( size_t i = prefix.size(); i < name.size() && name[i] >= '0' && name[i] <= '9'; i++ )
|
|
{
|
|
value = value * 10 + ( name[i] - '0' );
|
|
any = true;
|
|
}
|
|
return any ? value : -1;
|
|
};
|
|
|
|
auto activeIndex = [&]( size_t reservoirCell ) { return activeCellInfo->cellResultIndex( ReservoirCellIndex( reservoirCell ) ).value(); };
|
|
|
|
// Collect every active reconstructed-LGR cell with its refinement level (from REFINE) and its
|
|
// immediate parent cell (from the LGR hierarchy).
|
|
struct CellRef
|
|
{
|
|
size_t resultIndex;
|
|
int level;
|
|
size_t parentGlobal;
|
|
};
|
|
std::vector<CellRef> cellRefs;
|
|
for ( size_t gi = 1; gi < mainGrid->gridCount(); gi++ )
|
|
{
|
|
RigGridBase* g = mainGrid->gridByIndex( gi );
|
|
auto* lgr = dynamic_cast<RigLocalGrid*>( g );
|
|
if ( !lgr || !lgr->isReconstructedGrid() ) continue;
|
|
const int nameLevel = levelFromName( g->gridName() );
|
|
RigGridBase* parentGrid = lgr->parentGrid();
|
|
for ( size_t c = 0; c < g->cellCount(); c++ )
|
|
{
|
|
size_t global = g->reservoirCellIndex( c );
|
|
size_t ri = activeIndex( global );
|
|
if ( ri == cvf::UNDEFINED_SIZE_T ) continue;
|
|
int level = refine ? (int)std::lround( ( *refine )[global] ) : nameLevel;
|
|
if ( level <= 1 ) continue;
|
|
size_t parentGlobal = parentGrid->reservoirCellIndex( g->cell( c ).parentCellIndex() );
|
|
cellRefs.push_back( { ri, level, parentGlobal } );
|
|
}
|
|
}
|
|
if ( cellRefs.empty() ) return created;
|
|
|
|
const size_t tsCount = cellResults->cellScalarResults( sourceAddress ).size();
|
|
if ( tsCount == 0 ) return created;
|
|
|
|
// One output result per distinct level. Create every entry first: createResultEntry() pushes onto
|
|
// the backing storage and may reallocate it, which would invalidate any reference/pointer into the
|
|
// backing storage (sourceTs, volumes, previously fetched outTs). Only after all entries exist do we
|
|
// resolve the pointers and source references below.
|
|
std::map<int, RigEclipseResultAddress> outAddrByLevel;
|
|
for ( const CellRef& cr : cellRefs )
|
|
{
|
|
if ( outAddrByLevel.count( cr.level ) ) continue;
|
|
RigEclipseResultAddress outAddr( RiaDefines::ResultCatType::GENERATED,
|
|
sourceAddress.resultName() + QString( "_COARSE_L%1" ).arg( cr.level ) );
|
|
if ( !cellResults->hasResultEntry( outAddr ) ) cellResults->createResultEntry( outAddr, true );
|
|
outAddrByLevel.emplace( cr.level, outAddr );
|
|
}
|
|
|
|
std::map<int, std::vector<std::vector<double>>*> outByLevel;
|
|
for ( const auto& [level, outAddr] : outAddrByLevel )
|
|
{
|
|
std::vector<std::vector<double>>* outTs = cellResults->modifiableCellScalarResultTimesteps( outAddr );
|
|
if ( !outTs ) continue;
|
|
outTs->resize( tsCount );
|
|
outByLevel[level] = outTs;
|
|
created.push_back( outAddr );
|
|
}
|
|
|
|
// Safe to bind now that no further entries will be created.
|
|
const std::vector<std::vector<double>>& sourceTs = cellResults->cellScalarResults( sourceAddress );
|
|
const std::vector<double>& volumes = cellResults->cellScalarResults( volAddr, 0 );
|
|
|
|
// Pore volume (active-cell indexed) is the weight for the average; null if PORV is unavailable.
|
|
std::vector<double> porvTemp;
|
|
const std::vector<double>* porv = nullptr;
|
|
if ( mode == AggregationMode::PORE_VOLUME_WEIGHTED_AVERAGE )
|
|
{
|
|
porv = RigCaseCellResultsData::getResultIndexableStaticResult( activeCellInfo, cellResults, RiaResultNames::porv(), porvTemp );
|
|
}
|
|
|
|
for ( size_t ts = 0; ts < tsCount; ts++ )
|
|
{
|
|
const std::vector<double>& src = sourceTs[ts];
|
|
|
|
// Accumulation keyed by (level, immediate parent) - cells of different levels are never
|
|
// accumulated together. The zero-bulk-volume filter excludes hidden duplicates in both modes.
|
|
std::map<int, std::map<size_t, std::pair<double, double>>> acc;
|
|
for ( const CellRef& cr : cellRefs )
|
|
{
|
|
if ( cr.resultIndex >= src.size() || cr.resultIndex >= volumes.size() ) continue;
|
|
double v = src[cr.resultIndex];
|
|
if ( volumes[cr.resultIndex] <= 0.0 || v == HUGE_VAL ) continue;
|
|
if ( mode == AggregationMode::SUM )
|
|
{
|
|
auto& a = acc[cr.level][cr.parentGlobal];
|
|
a.first += v;
|
|
a.second += 1.0;
|
|
}
|
|
else
|
|
{
|
|
double w = ( porv && cr.resultIndex < porv->size() ) ? ( *porv )[cr.resultIndex] : volumes[cr.resultIndex];
|
|
if ( w <= 0.0 || w == HUGE_VAL ) continue;
|
|
auto& a = acc[cr.level][cr.parentGlobal];
|
|
a.first += v * w;
|
|
a.second += w;
|
|
}
|
|
}
|
|
|
|
for ( const auto& [level, outTs] : outByLevel )
|
|
{
|
|
std::vector<double>& out = ( *outTs )[ts];
|
|
out.assign( activeCellCount, HUGE_VAL ); // blank everywhere except this level's own cells
|
|
|
|
const std::map<size_t, std::pair<double, double>>& accLevel = acc[level];
|
|
for ( const CellRef& cr : cellRefs )
|
|
{
|
|
if ( cr.level != level || cr.resultIndex >= out.size() ) continue;
|
|
auto it = accLevel.find( cr.parentGlobal );
|
|
if ( it != accLevel.end() && it->second.second > 0.0 )
|
|
{
|
|
out[cr.resultIndex] = ( mode == AggregationMode::SUM ) ? it->second.first : it->second.first / it->second.second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return created;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RigNestedHybridGridResultTools::assignValuesToLgrs( RigCaseCellResultsData* cellResults, std::vector<double>& values )
|
|
{
|
|
if ( !cellResults ) return;
|
|
|
|
RigMainGrid* mainGrid = cellResults->mainGrid();
|
|
RigActiveCellInfo* activeCellInfo = cellResults->activeCellInfo();
|
|
if ( !mainGrid || !activeCellInfo ) return;
|
|
|
|
const std::map<size_t, size_t>& sourceCells = mainGrid->nestedHybridLgrSourceCells();
|
|
if ( sourceCells.empty() || values.empty() ) return;
|
|
|
|
const size_t totalCellCount = mainGrid->totalCellCount();
|
|
if ( values.size() >= totalCellCount ) return; // full-length array already covering the LGR cells
|
|
|
|
// Full-length (all-cells) array loaded after the reconstruction: the file array covers only the
|
|
// original flat cells (the LGR cells are appended at the end of the grid), so it is indexed by
|
|
// global reservoir cell index, not by active-cell result index. Extend it the same way
|
|
// RigNestedHybridGridReconstructor::extendFullLengthResults() extends the already-loaded ones.
|
|
// The original flat cell count is the main grid's own cell count (the LGR cells all live in the
|
|
// appended local grids, including filler cells without a source mapping).
|
|
const size_t origCellCount = mainGrid->cellCount();
|
|
if ( values.size() == origCellCount )
|
|
{
|
|
values.resize( totalCellCount, std::numeric_limits<double>::infinity() );
|
|
for ( const auto& [lgrReservoirCellIndex, flatReservoirCellIndex] : sourceCells )
|
|
{
|
|
values[lgrReservoirCellIndex] = values[flatReservoirCellIndex];
|
|
}
|
|
return;
|
|
}
|
|
|
|
const size_t activeCellCount = activeCellInfo->reservoirActiveCellCount();
|
|
if ( values.size() < activeCellCount )
|
|
{
|
|
values.resize( activeCellCount, std::numeric_limits<double>::infinity() );
|
|
}
|
|
|
|
for ( const auto& [lgrReservoirCellIndex, flatReservoirCellIndex] : sourceCells )
|
|
{
|
|
size_t lgrResultIndex = activeCellInfo->cellResultIndex( ReservoirCellIndex( lgrReservoirCellIndex ) ).value();
|
|
size_t flatResultIndex = activeCellInfo->cellResultIndex( ReservoirCellIndex( flatReservoirCellIndex ) ).value();
|
|
|
|
if ( lgrResultIndex != cvf::UNDEFINED_SIZE_T && flatResultIndex != cvf::UNDEFINED_SIZE_T && lgrResultIndex < values.size() &&
|
|
flatResultIndex < values.size() )
|
|
{
|
|
values[lgrResultIndex] = values[flatResultIndex];
|
|
}
|
|
}
|
|
}
|