RigWellTargetMapping: Extract private implementation into RigWellTargetMappingTools

Move private methods, DataContainer, and ClusterStatistics from
RigWellTargetMapping into a new RigWellTargetMappingTools class to reduce
header complexity. Change generateEnsembleCandidates to accept
std::vector<RimEclipseCase*> instead of RimEclipseCaseEnsemble.
This commit is contained in:
Magne Sjaastad committed 2026-03-04 07:37:19 +01:00
1 parent ed8917dc1b
commit f5234f0e06
8 files changed
+1798 -1043

No files matched your search

@@ -394,7 +394,7 @@ void RimWellTargetMapping::generateEnsembleStatistics()
RigWellTargetMapping::ClusteringLimits limits = getClusteringLimits();
RigFloodingSettings floodingSettings( m_oilFloodingType(), m_userDefinedFloodingOil(), m_gasFloodingType(), m_userDefinedFloodingGas() );
RimRegularGridCase* regularGridCase = RigWellTargetMapping::generateEnsembleCandidates( *ensemble,
RimRegularGridCase* regularGridCase = RigWellTargetMapping::generateEnsembleCandidates( ensemble->cases(),
m_timeStep(),
resultGridCellCount,
m_volumeType(),
@@ -24,6 +24,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RigWellResultFrame.h
${CMAKE_CURRENT_LIST_DIR}/RigWellResultPoint.h
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetMapping.h
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetMappingTools.h
${CMAKE_CURRENT_LIST_DIR}/RigWellLogData.h
${CMAKE_CURRENT_LIST_DIR}/RigWellLogLasFile.h
${CMAKE_CURRENT_LIST_DIR}/RigWellLogCsvFile.h
@@ -54,6 +55,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RigWellResultFrame.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellResultPoint.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetMapping.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetMappingTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellLogData.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellLogLasFile.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellLogCsvFile.cpp
File diff suppressed because it is too large. Load diff
@@ -18,23 +18,14 @@
#pragma once
#include "cafVecIjk.h"
#include "cvfBoundingBox.h"
#include "cvfStructGrid.h"
#include "RigEclipseResultAddress.h"
#include <list>
#include <map>
#include <optional>
#include <utility>
#include "cvfVector3.h"
#include <vector>
class RigActiveCellInfo;
class RigCaseCellResultsData;
class RigMainGrid;
class RimEclipseCase;
class RimEclipseCaseEnsemble;
class RimRegularGridCase;
class RigFloodingSettings;
@@ -45,8 +36,6 @@ class RigFloodingSettings;
class RigWellTargetMapping
{
public:
using CellFaceType = cvf::StructGridInterface::FaceType;
enum class VolumeType
{
OIL,
@@ -81,20 +70,6 @@ public:
std::vector<double> filter;
};
struct DataContainer
{
std::vector<double> volume;
std::vector<double> saturationOil;
std::vector<double> saturationGas;
std::vector<double> pressure;
std::vector<double> permeabilityX;
std::vector<double> permeabilityNNC;
std::vector<double> transmissibilityX;
std::vector<double> transmissibilityY;
std::vector<double> transmissibilityZ;
const std::vector<double>* transmissibilityNNC;
};
static void generateCandidates( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
VolumeType volumeType,
@@ -113,154 +88,14 @@ public:
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings );
static RimRegularGridCase* generateEnsembleCandidates( RimEclipseCaseEnsemble& ensemble,
size_t timeStepIdx,
const cvf::Vec3st& resultGridCellCount,
VolumeType volumeType,
VolumesType volumesType,
VolumeResultType volumeResultType,
const RigFloodingSettings& floodingSettings,
const ClusteringLimits& limits );
static RimRegularGridCase* generateEnsembleCandidates( const std::vector<RimEclipseCase*>& cases,
size_t timeStepIdx,
const cvf::Vec3st& resultGridCellCount,
VolumeType volumeType,
VolumesType volumesType,
VolumeResultType volumeResultType,
const RigFloodingSettings& floodingSettings,
const ClusteringLimits& limits );
static QString wellTargetResultName();
class ClusterStatistics
{
public:
ClusterStatistics()
: id( -1 )
, numCells( 0 )
, totalPorvSoil( 0.0 )
, totalPorvSgas( 0.0 )
, totalPorvSoilAndSgas( 0.0 )
, totalFipOil( 0.0 )
, totalFipGas( 0.0 )
, totalRfipOil( 0.0 )
, totalRfipGas( 0.0 )
, totalSfipOil( 0.0 )
, totalSfipGas( 0.0 )
, permeability( 0.0 )
, pressure( 0.0 )
{
}
int id;
size_t numCells;
double totalPorvSoil;
double totalPorvSgas;
double totalPorvSoilAndSgas;
double totalFipOil;
double totalFipGas;
double totalRfipOil;
double totalRfipGas;
double totalSfipOil;
double totalSfipGas;
double permeability;
double pressure;
};
private:
static std::optional<caf::VecIjk0> findStartCell( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
const VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
const std::vector<int>& clusters );
static void growCluster( RimEclipseCase* eclipseCase,
const caf::VecIjk0& startCell,
const VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
std::vector<int>& clusters,
int clusterId,
size_t timeStepIdx,
int maxIterations );
static std::vector<size_t> findCandidates( RimEclipseCase* eclipseCase,
const std::vector<size_t>& previousCells,
const VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
std::vector<int>& clusters );
static void assignClusterIdToCells( const RigActiveCellInfo& activeCellInfo,
const std::vector<size_t>& cells,
std::vector<int>& clusters,
int clusterId );
static std::optional<size_t> getActiveCellCount( RimEclipseCase* eclipseCase );
static void createDynamicResultEntry( RigCaseCellResultsData* resultsData, const RigEclipseResultAddress& address );
static void
createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<int>& clusterIds, size_t timeStepIdx );
static void
createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<double>& values, size_t timeStepIdx );
static void createStaticResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<int>& intValues );
static void createStaticResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<double>& values );
static void createResultVectorIfDefined( RimEclipseCase& eclipseCase,
const QString& resultName,
const std::vector<double>& values,
int timeStepIdx = -1 );
static double getValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
CellFaceType face,
size_t resultIndex );
static double getTransmissibilityValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
CellFaceType face,
size_t resultIndex,
size_t neighborResultIndex );
static std::vector<RigWellTargetMapping::ClusterStatistics> generateStatistics( RimEclipseCase* eclipseCase,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
int numClustersFound,
size_t timeStepIdx,
const QString& clusterResultName );
static void computeStatisticsAndCreateVectors( RimEclipseCase& targetCase,
const QString& resultName,
const std::vector<std::vector<double>>& vec );
static void accumulateResultsForSingleCase( RimEclipseCase& eclipseCase,
RimEclipseCase& targetCase,
std::map<QString, std::vector<std::vector<double>>>& resultNamesAndSamples,
std::vector<int>& occupancy,
size_t timeStepIdx );
static cvf::BoundingBox computeBoundingBoxForResult( RimEclipseCase& eclipseCase, const QString& resultName, size_t timeStepIndex );
static std::list<std::pair<std::pair<size_t, CellFaceType>, size_t>> nncConnectionCellAndResult( size_t cellIdx, RigMainGrid* mainGrid );
static std::vector<double> loadVectorByName( RigCaseCellResultsData& resultsData, const QString& resultName, size_t timeStepIdx );
static std::vector<double> loadOilVectorByName( RigCaseCellResultsData& resultsData,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings );
static std::vector<double> loadGasVectorByName( RigCaseCellResultsData& resultsData,
RiaDefines::EclipseUnitSystem unitsType,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings );
static QString getOilVectorName( VolumesType volumesType );
static QString getGasVectorName( VolumesType volumesType );
static bool isSaturationSufficient( const VolumeType volumeType, const DataContainer& data, const ClusteringLimits& limits, size_t idx );
};
@@ -0,0 +1,879 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024- 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 "RigWellTargetMappingTools.h"
#include "RiaDefines.h"
#include "RiaEclipseUnitTools.h"
#include "RiaLogging.h"
#include "RiaResultNames.h"
#include "RiaWeightedMeanCalculator.h"
#include "RigCaseCellResultsData.h"
#include "RigCell.h"
#include "RigEclipseCaseData.h"
#include "RigEclipseResultInfo.h"
#include "RigFloodingSettings.h"
#include "RigGridBase.h"
#include "RigHydrocarbonFlowTools.h"
#include "RigMainGrid.h"
#include "RigNNCData.h"
#include "RigNncConnection.h"
#include "RigStatisticsMath.h"
#include "RigStatisticsTools.h"
#include "RimEclipseCase.h"
#include "cafAssert.h"
#include "cafVecIjk.h"
#include "cvfMath.h"
#include "cvfStructGrid.h"
#include <algorithm>
#include <limits>
#include <set>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigWellTargetMappingTools::getValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
CellFaceType face,
ActiveCellIndex resultIndex )
{
if ( face == cvf::StructGridInterface::FaceType::POS_I || face == cvf::StructGridInterface::FaceType::NEG_I )
return x[resultIndex.value()];
if ( face == cvf::StructGridInterface::FaceType::POS_J || face == cvf::StructGridInterface::FaceType::NEG_J )
return y[resultIndex.value()];
if ( face == cvf::StructGridInterface::FaceType::POS_K || face == cvf::StructGridInterface::FaceType::NEG_K )
return z[resultIndex.value()];
return std::numeric_limits<double>::infinity();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigWellTargetMappingTools::getTransmissibilityValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
CellFaceType face,
ActiveCellIndex resultIndex,
ActiveCellIndex neighborResultIndex )
{
// For negative directions (NEG_I, NEG_J, NEG_K) use the value from the neighbor cell
bool isPos = face == cvf::StructGridInterface::FaceType::POS_I || face == cvf::StructGridInterface::FaceType::POS_J ||
face == cvf::StructGridInterface::FaceType::POS_K;
ActiveCellIndex index = isPos ? resultIndex : neighborResultIndex;
return getValueForFace( x, y, z, face, index );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RigWellTargetMappingTools::getOilVectorName( VolumesType volumesType )
{
switch ( volumesType )
{
case VolumesType::RESERVOIR_VOLUMES_COMPUTED:
return RiaResultNames::riPorvSoil();
case VolumesType::RESERVOIR_VOLUMES:
return "RFIPOIL";
case VolumesType::SURFACE_VOLUMES_SFIP:
return "SFIPOIL";
case VolumesType::SURFACE_VOLUMES_FIP:
return "FIPOIL";
default:
{
CAF_ASSERT( false );
return "";
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RigWellTargetMappingTools::getGasVectorName( VolumesType volumesType )
{
switch ( volumesType )
{
case VolumesType::RESERVOIR_VOLUMES_COMPUTED:
return RiaResultNames::riPorvSgas();
case VolumesType::RESERVOIR_VOLUMES:
return "RFIPGAS";
case VolumesType::SURFACE_VOLUMES_SFIP:
return "SFIPGAS";
case VolumesType::SURFACE_VOLUMES_FIP:
return "FIPGAS";
default:
{
CAF_ASSERT( false );
return "";
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RigWellTargetMappingTools::isSaturationSufficient( VolumeType volumeType,
const DataContainer& data,
const ClusteringLimits& limits,
ActiveCellIndex idx )
{
bool needsValidOil = volumeType == VolumeType::OIL || volumeType == VolumeType::HYDROCARBON;
bool needsValidGas = volumeType == VolumeType::GAS || volumeType == VolumeType::HYDROCARBON;
// For hydrocarbon it is enough that one of the saturations is above the limit.
if ( needsValidOil && data.saturationOil[idx.value()] >= limits.saturationOil ) return true;
if ( needsValidGas && data.saturationGas[idx.value()] >= limits.saturationGas ) return true;
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::assignClusterIdToCells( const RigActiveCellInfo& activeCellInfo,
const std::vector<ReservoirCellIndex>& cells,
std::vector<int>& clusters,
int clusterId )
{
for ( ReservoirCellIndex reservoirCellIdx : cells )
{
ActiveCellIndex resultIndex = activeCellInfo.cellResultIndex( reservoirCellIdx );
if ( resultIndex.value() != cvf::UNDEFINED_SIZE_T ) clusters[resultIndex.value()] = clusterId;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::list<std::pair<std::pair<ReservoirCellIndex, RigWellTargetMappingTools::CellFaceType>, size_t>>
RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex cellIdx, RigMainGrid* mainGrid )
{
std::list<std::pair<std::pair<ReservoirCellIndex, CellFaceType>, size_t>> foundCells;
if ( mainGrid->nncData() == nullptr ) return foundCells;
auto& connections = mainGrid->nncData()->allConnections();
for ( size_t i = 0; i < connections.size(); i++ )
{
if ( connections[i].c1GlobIdx() == cellIdx.value() )
{
foundCells.push_back(
std::make_pair( std::make_pair( ReservoirCellIndex( connections[i].c2GlobIdx() ), connections[i].face() ), i ) );
}
}
return foundCells;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::createDynamicResultEntry( RigCaseCellResultsData* resultsData, const RigEclipseResultAddress& address )
{
if ( !resultsData->hasResultEntry( address ) )
{
resultsData->createResultEntry( address, false );
RigEclipseResultAddress addrToMaxTimeStepCountResult;
resultsData->maxTimeStepCount( &addrToMaxTimeStepCountResult );
const std::vector<RigEclipseTimeStepInfo> timeStepInfos = resultsData->timeStepInfos( addrToMaxTimeStepCountResult );
resultsData->setTimeStepInfos( address, timeStepInfos );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::createResultVector( RimEclipseCase& eclipseCase,
const QString& resultName,
const std::vector<int>& clusterIds,
size_t timeStepIdx )
{
RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, resultName );
auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
createDynamicResultEntry( resultsData, resultAddress );
std::vector<double>* resultVector = resultsData->modifiableCellScalarResult( resultAddress, timeStepIdx );
resultVector->resize( clusterIds.size(), std::numeric_limits<double>::infinity() );
std::fill( resultVector->begin(), resultVector->end(), std::numeric_limits<double>::infinity() );
for ( size_t idx = 0; idx < clusterIds.size(); idx++ )
{
if ( clusterIds[idx] > 0 )
{
resultVector->at( idx ) = 1.0 * clusterIds[idx];
}
}
std::set<int> uniqueClusterIds( clusterIds.begin(), clusterIds.end() );
uniqueClusterIds.erase( 0 ); // Remove unassigned cluster id
resultsData->recalculateStatistics( resultAddress, uniqueClusterIds );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::createResultVector( RimEclipseCase& eclipseCase,
const QString& resultName,
const std::vector<double>& values,
size_t timeStepIdx )
{
RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName );
auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
createDynamicResultEntry( resultsData, resultAddress );
auto resultVector = resultsData->modifiableCellScalarResult( resultAddress, timeStepIdx );
resultVector->resize( values.size(), std::numeric_limits<double>::infinity() );
std::copy( values.begin(), values.end(), resultVector->begin() );
resultsData->recalculateStatistics( resultAddress );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::createStaticResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<int>& intValues )
{
RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName );
auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
resultsData->addStaticScalarResult( RiaDefines::ResultCatType::GENERATED, resultName, false, intValues.size() );
std::vector<double>* resultVector = resultsData->modifiableCellScalarResult( resultAddress, 0 );
resultVector->resize( intValues.size(), std::numeric_limits<double>::infinity() );
std::fill( resultVector->begin(), resultVector->end(), std::numeric_limits<double>::infinity() );
for ( size_t idx = 0; idx < intValues.size(); idx++ )
{
if ( intValues[idx] > 0 )
{
resultVector->at( idx ) = intValues[idx];
}
}
resultsData->recalculateStatistics( resultAddress );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::createStaticResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<double>& values )
{
RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName );
auto resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
resultsData->addStaticScalarResult( RiaDefines::ResultCatType::GENERATED, resultName, false, values.size() );
std::vector<double>* resultVector = resultsData->modifiableCellScalarResult( resultAddress, 0 );
resultVector->resize( values.size(), std::numeric_limits<double>::infinity() );
std::copy( values.begin(), values.end(), resultVector->begin() );
resultsData->recalculateStatistics( resultAddress );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::createResultVectorIfDefined( RimEclipseCase& eclipseCase,
const QString& resultName,
const std::vector<double>& values,
int timeStepIdx )
{
// Avoid creating the result vector if all values are inf/nan
if ( std::all_of( values.begin(), values.end(), []( auto v ) { return std::isinf( v ) || std::isnan( v ); } ) ) return;
if ( timeStepIdx < 0 )
{
createStaticResultVector( eclipseCase, resultName, values );
}
else
{
createResultVector( eclipseCase, resultName, values, timeStepIdx );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<caf::VecIjk0> RigWellTargetMappingTools::findStartCell( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
const std::vector<int>& clusters )
{
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData )
{
RiaLogging::error( "No results data found for eclipse case" );
return {};
}
std::optional<ReservoirCellIndex> startCell;
double maxVolume = -std::numeric_limits<double>::max();
const size_t numReservoirCells = resultsData->activeCellInfo()->reservoirCellCount();
for ( ReservoirCellIndex reservoirCellIdx( 0 ); reservoirCellIdx.value() < numReservoirCells; ++reservoirCellIdx )
{
ActiveCellIndex resultIndex = resultsData->activeCellInfo()->cellResultIndex( reservoirCellIdx );
if ( resultIndex.value() != cvf::UNDEFINED_SIZE_T && clusters[resultIndex.value()] == 0 )
{
const double cellVolume = data.volume[resultIndex.value()];
const double cellPressure = data.pressure[resultIndex.value()];
const bool isSaturationValid = isSaturationSufficient( volumeType, data, limits, resultIndex );
const double cellPermeabiltyX = data.permeabilityX[resultIndex.value()];
const bool permeabilityValid = ( cellPermeabiltyX >= limits.permeability );
const bool filterValue = !std::isinf( filterVector[resultIndex.value()] ) && filterVector[resultIndex.value()] > 0.0;
if ( cellVolume > maxVolume && cellPressure >= limits.pressure && permeabilityValid && filterValue && isSaturationValid )
{
maxVolume = cellVolume;
startCell = ReservoirCellIndex( reservoirCellIdx );
}
}
}
if ( !startCell ) return {};
return eclipseCase->mainGrid()->ijkFromCellIndex( startCell->value() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::growCluster( RimEclipseCase* eclipseCase,
const caf::VecIjk0& startCell,
VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
std::vector<int>& clusters,
int clusterId,
size_t timeStepIdx,
int maxIterations )
{
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
// Initially only the start cell is found
ReservoirCellIndex reservoirCellIdx( eclipseCase->mainGrid()->cellIndexFromIJK( startCell.i(), startCell.j(), startCell.k() ) );
std::vector<ReservoirCellIndex> foundCells = { reservoirCellIdx };
assignClusterIdToCells( *resultsData->activeCellInfo(), foundCells, clusters, clusterId );
for ( int i = 0; i < maxIterations; i++ )
{
foundCells = findCandidates( eclipseCase, foundCells, volumeType, limits, data, filterVector, clusters );
if ( foundCells.empty() ) break;
assignClusterIdToCells( *resultsData->activeCellInfo(), foundCells, clusters, clusterId );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<ReservoirCellIndex> RigWellTargetMappingTools::findCandidates( RimEclipseCase* eclipseCase,
const std::vector<ReservoirCellIndex>& previousCells,
VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
std::vector<int>& clusters )
{
std::vector<ReservoirCellIndex> candidates;
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
auto mainGrid = eclipseCase->eclipseCaseData()->mainGrid();
const std::vector<CellFaceType> faces = {
cvf::StructGridInterface::FaceType::POS_I,
cvf::StructGridInterface::FaceType::NEG_I,
cvf::StructGridInterface::FaceType::POS_J,
cvf::StructGridInterface::FaceType::NEG_J,
cvf::StructGridInterface::FaceType::POS_K,
cvf::StructGridInterface::FaceType::NEG_K,
};
for ( ReservoirCellIndex cellIdx : previousCells )
{
const RigCell& cell = mainGrid->cell( cellIdx.value() );
if ( cell.isInvalid() ) continue;
RigGridBase* grid = cell.hostGrid();
size_t gridLocalCellIndex = cell.gridLocalCellIndex();
ActiveCellIndex resultIndex = resultsData->activeCellInfo()->cellResultIndex( cellIdx );
size_t i, j, k;
grid->ijkFromCellIndex( gridLocalCellIndex, &i, &j, &k );
for ( CellFaceType face : faces )
{
size_t gridLocalNeighborCellIdx;
if ( grid->cellIJKNeighbor( i, j, k, face, &gridLocalNeighborCellIdx ) )
{
ReservoirCellIndex neighborResvCellIdx( grid->reservoirCellIndex( gridLocalNeighborCellIdx ) );
ActiveCellIndex neighborResultIndex = resultsData->activeCellInfo()->cellResultIndex( neighborResvCellIdx );
if ( neighborResultIndex.value() != cvf::UNDEFINED_SIZE_T && clusters[neighborResultIndex.value()] == 0 )
{
double permeability = data.permeabilityX[neighborResultIndex.value()];
double transmissibility = getTransmissibilityValueForFace( data.transmissibilityX,
data.transmissibilityY,
data.transmissibilityZ,
face,
resultIndex,
neighborResultIndex );
bool filterValue = !std::isinf( filterVector[neighborResultIndex.value()] ) &&
filterVector[neighborResultIndex.value()] > 0.0;
const bool isSaturationValid = isSaturationSufficient( volumeType, data, limits, neighborResultIndex );
if ( data.pressure[neighborResultIndex.value()] > limits.pressure && permeability > limits.permeability &&
transmissibility > limits.transmissibility && filterValue && isSaturationValid )
{
candidates.push_back( neighborResvCellIdx );
clusters[neighborResultIndex.value()] = -1;
}
}
}
}
if ( data.transmissibilityNNC != nullptr )
{
auto nncCells = nncConnectionCellAndResult( cellIdx, mainGrid );
for ( auto& [cellInfo, nncResultIdx] : nncCells )
{
auto& [otherCellIdx, face] = cellInfo;
double transmissibility = data.transmissibilityNNC->at( nncResultIdx );
ActiveCellIndex otherResultIndex = resultsData->activeCellInfo()->cellResultIndex( otherCellIdx );
double permeability = data.permeabilityX[otherResultIndex.value()];
bool filterValue = !std::isinf( filterVector[otherResultIndex.value()] ) && filterVector[otherResultIndex.value()] > 0.0;
const bool isSaturationValid = isSaturationSufficient( volumeType, data, limits, otherResultIndex );
if ( data.pressure[otherResultIndex.value()] > limits.pressure && permeability > limits.permeability &&
transmissibility > limits.transmissibility && filterValue && isSaturationValid )
{
candidates.push_back( otherCellIdx );
clusters[otherResultIndex.value()] = -1;
}
}
}
}
return candidates;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<size_t> RigWellTargetMappingTools::getActiveCellCount( RimEclipseCase* eclipseCase )
{
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData ) return {};
return resultsData->activeCellInfo()->reservoirActiveCellCount();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double>
RigWellTargetMappingTools::loadVectorByName( RigCaseCellResultsData& resultsData, const QString& resultName, size_t timeStepIdx )
{
RigEclipseResultAddress address( RiaDefines::ResultCatType::DYNAMIC_NATIVE, resultName );
if ( !resultsData.ensureKnownResultLoaded( address ) ) return {};
return resultsData.cellScalarResults( address, timeStepIdx );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RigWellTargetMappingTools::loadOilVectorByName( RigCaseCellResultsData& resultsData,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings )
{
std::vector<double> volume = loadVectorByName( resultsData, getOilVectorName( volumesType ), timeStepIdx );
if ( volumeResultType == VolumeResultType::MOBILE )
{
std::vector<double> residualOil = RigHydrocarbonFlowTools::residualOilData( resultsData,
RigHydrocarbonFlowTools::ResultType::MOBILE_OIL,
floodingSettings,
volume.size() );
if ( volumesType == VolumesType::RESERVOIR_VOLUMES_COMPUTED )
{
const std::vector<double>& porvResults =
resultsData.cellScalarResults( RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::porv() ), 0 );
for ( size_t i = 0; i < volume.size(); i++ )
{
volume[i] = std::max( volume[i] - ( porvResults[i] * residualOil[i] ), 0.0 );
}
}
else
{
const std::vector<double>& soilResults =
resultsData.cellScalarResults( RigEclipseResultAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::soil() ),
timeStepIdx );
for ( size_t i = 0; i < volume.size(); i++ )
{
if ( soilResults[i] != 0.0 )
{
volume[i] = std::max( volume[i] * ( soilResults[i] - residualOil[i] ) / soilResults[i], 0.0 );
}
else
{
volume[i] = 0.0;
}
}
}
}
return volume;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RigWellTargetMappingTools::loadGasVectorByName( RigCaseCellResultsData& resultsData,
RiaDefines::EclipseUnitSystem unitsType,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings )
{
std::vector<double> volume = loadVectorByName( resultsData, getGasVectorName( volumesType ), timeStepIdx );
if ( volumeResultType == VolumeResultType::MOBILE )
{
std::vector<double> residualGas = RigHydrocarbonFlowTools::residualGasData( resultsData,
RigHydrocarbonFlowTools::ResultType::MOBILE_GAS,
floodingSettings,
volume.size() );
if ( volumesType == VolumesType::RESERVOIR_VOLUMES_COMPUTED )
{
const std::vector<double>& porvResults =
resultsData.cellScalarResults( RigEclipseResultAddress( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::porv() ), 0 );
for ( size_t i = 0; i < volume.size(); i++ )
{
volume[i] = std::max( volume[i] - ( porvResults[i] * residualGas[i] ), 0.0 );
}
}
else
{
const std::vector<double>& sgasResults =
resultsData.cellScalarResults( RigEclipseResultAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::sgas() ),
timeStepIdx );
for ( size_t i = 0; i < volume.size(); i++ )
{
if ( sgasResults[i] != 0.0 )
{
volume[i] = std::max( volume[i] * ( sgasResults[i] - residualGas[i] ) / sgasResults[i], 0.0 );
}
else
{
volume[i] = 0.0;
}
}
}
}
// Convert gas volumes to oil equivalents
for ( size_t i = 0; i < volume.size(); i++ )
{
volume[i] = RiaEclipseUnitTools::convertSurfaceGasFlowRateToOilEquivalents( unitsType, volume[i] );
}
return volume;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RigWellTargetMappingTools::ClusterStatistics> RigWellTargetMappingTools::generateStatistics( RimEclipseCase* eclipseCase,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
int numClustersFound,
size_t timeStepIdx,
const QString& clusterResultName )
{
std::vector<ClusterStatistics> statistics( numClustersFound );
auto resultsData = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData ) return statistics;
auto loadData = []( RigCaseCellResultsData* resultsData, RiaDefines::ResultCatType categoryType, const QString& name, size_t timeStepIdx )
{
RigEclipseResultAddress address( categoryType, name );
std::vector<double> values;
if ( resultsData->ensureKnownResultLoaded( address ) )
{
values = resultsData->cellScalarResults( address, timeStepIdx );
}
return values;
};
const std::vector<double> porv = loadData( resultsData, RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::porv(), 0 );
const std::vector<double> porvSoil =
loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoil(), timeStepIdx );
const std::vector<double> porvSgas =
loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSgas(), timeStepIdx );
const std::vector<double> porvSoilAndSgas =
loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoilSgas(), timeStepIdx );
const std::vector<double> fipOil = loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, "FIPOIL", timeStepIdx );
const std::vector<double> fipGas = loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, "FIPGAS", timeStepIdx );
const std::vector<double> sfipOil = loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, "SFIPOIL", timeStepIdx );
const std::vector<double> sfipGas = loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, "SFIPGAS", timeStepIdx );
const std::vector<double> rfipOil = loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, "RFIPOIL", timeStepIdx );
const std::vector<double> rfipGas = loadData( resultsData, RiaDefines::ResultCatType::DYNAMIC_NATIVE, "RFIPGAS", timeStepIdx );
RigEclipseResultAddress clusterAddress( RiaDefines::ResultCatType::GENERATED, clusterResultName );
resultsData->ensureKnownResultLoaded( clusterAddress );
const std::vector<double>& clusterIds = resultsData->cellScalarResults( clusterAddress, timeStepIdx );
std::vector<RiaWeightedMeanCalculator<double>> permeabilityCalculators( numClustersFound );
std::vector<RiaWeightedMeanCalculator<double>> pressureCalculators( numClustersFound );
for ( size_t idx = 0; idx < clusterIds.size(); idx++ )
{
if ( !std::isinf( clusterIds[idx] ) && static_cast<int>( clusterIds[idx] ) > 0 )
{
size_t i = clusterIds[idx] - 1;
if ( i < static_cast<size_t>( numClustersFound ) )
{
statistics[i].id = clusterIds[idx];
statistics[i].numCells++;
if ( idx < porvSoil.size() ) statistics[i].totalPorvSoil += porvSoil[idx];
if ( idx < porvSgas.size() ) statistics[i].totalPorvSgas += porvSgas[idx];
if ( idx < porvSoilAndSgas.size() ) statistics[i].totalPorvSoilAndSgas += porvSoilAndSgas[idx];
if ( idx < fipOil.size() ) statistics[i].totalFipOil += fipOil[idx];
if ( idx < fipGas.size() ) statistics[i].totalFipGas += fipGas[idx];
if ( idx < rfipOil.size() ) statistics[i].totalRfipOil += rfipOil[idx];
if ( idx < rfipGas.size() ) statistics[i].totalRfipGas += rfipGas[idx];
if ( idx < sfipOil.size() ) statistics[i].totalSfipOil += sfipOil[idx];
if ( idx < sfipGas.size() ) statistics[i].totalSfipGas += sfipGas[idx];
permeabilityCalculators[i].addValueAndWeight( permeabilityX[idx], porv[idx] );
pressureCalculators[i].addValueAndWeight( pressure[idx], porv[idx] );
}
}
}
for ( int i = 0; i < numClustersFound; i++ )
{
statistics[i].permeability = permeabilityCalculators[i].weightedMean();
statistics[i].pressure = pressureCalculators[i].weightedMean();
// Invalidate results for empty vectors
if ( porvSoil.empty() ) statistics[i].totalPorvSoil = std::numeric_limits<double>::infinity();
if ( porvSgas.empty() ) statistics[i].totalPorvSgas = std::numeric_limits<double>::infinity();
if ( porvSoilAndSgas.empty() ) statistics[i].totalPorvSoilAndSgas = std::numeric_limits<double>::infinity();
if ( fipOil.empty() ) statistics[i].totalFipOil = std::numeric_limits<double>::infinity();
if ( fipGas.empty() ) statistics[i].totalFipGas = std::numeric_limits<double>::infinity();
if ( rfipOil.empty() ) statistics[i].totalRfipOil = std::numeric_limits<double>::infinity();
if ( rfipGas.empty() ) statistics[i].totalRfipGas = std::numeric_limits<double>::infinity();
if ( sfipOil.empty() ) statistics[i].totalSfipOil = std::numeric_limits<double>::infinity();
if ( sfipGas.empty() ) statistics[i].totalSfipGas = std::numeric_limits<double>::infinity();
}
return statistics;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::computeStatisticsAndCreateVectors( RimEclipseCase& targetCase,
const QString& resultName,
const std::vector<std::vector<double>>& vec )
{
const RigCaseCellResultsData* targetResultsData = targetCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !targetResultsData ) return;
const RigActiveCellInfo* targetActiveCellInfo = targetResultsData->activeCellInfo();
if ( !targetActiveCellInfo ) return;
const size_t targetNumActiveCells = targetActiveCellInfo->reservoirActiveCellCount();
int nCells = static_cast<int>( targetNumActiveCells );
std::vector<double> p10Results( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> p50Results( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> p90Results( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> meanResults( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> minResults( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> maxResults( nCells, std::numeric_limits<double>::infinity() );
#pragma omp parallel for
for ( int i = 0; i < nCells; i++ )
{
size_t numSamples = vec.size();
std::vector<double> samples( numSamples, 0.0 );
for ( size_t s = 0; s < numSamples; s++ )
samples[s] = vec[s][i];
double p10, p50, p90, mean;
RigStatisticsMath::calculateStatisticsCurves( samples, &p10, &p50, &p90, &mean, RigStatisticsMath::PercentileStyle::SWITCHED );
if ( RigStatisticsTools::isValidNumber( p10 ) ) p10Results[i] = p10;
if ( RigStatisticsTools::isValidNumber( p50 ) ) p50Results[i] = p50;
if ( RigStatisticsTools::isValidNumber( p90 ) ) p90Results[i] = p90;
if ( RigStatisticsTools::isValidNumber( mean ) ) meanResults[i] = mean;
double minValue = RigStatisticsTools::minimumValue( samples );
if ( RigStatisticsTools::isValidNumber( minValue ) && minValue < std::numeric_limits<double>::max() ) minResults[i] = minValue;
double maxValue = RigStatisticsTools::maximumValue( samples );
if ( RigStatisticsTools::isValidNumber( maxValue ) && maxValue > -std::numeric_limits<double>::max() ) maxResults[i] = maxValue;
}
createResultVectorIfDefined( targetCase, resultName + "_P10", p10Results );
createResultVectorIfDefined( targetCase, resultName + "_P50", p50Results );
createResultVectorIfDefined( targetCase, resultName + "_P90", p90Results );
createResultVectorIfDefined( targetCase, resultName + "_MEAN", meanResults );
createResultVectorIfDefined( targetCase, resultName + "_MIN", minResults );
createResultVectorIfDefined( targetCase, resultName + "_MAX", maxResults );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWellTargetMappingTools::accumulateResultsForSingleCase( RimEclipseCase& eclipseCase,
RimEclipseCase& targetCase,
std::map<QString, std::vector<std::vector<double>>>& resultNamesAndSamples,
std::vector<int>& occupancy,
size_t timeStepIdx )
{
RigCaseCellResultsData* resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
if ( !resultsData ) return;
const RigMainGrid* mainGrid = eclipseCase.mainGrid();
if ( !mainGrid ) return;
const RigActiveCellInfo* activeCellInfo = resultsData->activeCellInfo();
if ( !activeCellInfo ) return;
const RigCaseCellResultsData* targetResultsData = targetCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
const RigActiveCellInfo* targetActiveCellInfo = targetResultsData->activeCellInfo();
const size_t targetNumReservoirCells = targetActiveCellInfo->reservoirCellCount();
const size_t targetNumActiveCells = targetActiveCellInfo->reservoirActiveCellCount();
occupancy.resize( targetNumActiveCells, 0 );
RigEclipseResultAddress clustersNumAddress( RiaDefines::ResultCatType::GENERATED, RigWellTargetMapping::wellTargetResultName() );
resultsData->ensureKnownResultLoaded( clustersNumAddress );
const std::vector<double>& clusterNum = resultsData->cellScalarResults( clustersNumAddress, timeStepIdx );
std::map<QString, const std::vector<double>*> namedInputVector;
for ( const auto& [resultName, vec] : resultNamesAndSamples )
{
RigEclipseResultAddress resultAddress( RiaDefines::ResultCatType::GENERATED, resultName );
resultsData->ensureKnownResultLoaded( resultAddress );
const std::vector<double>& resultVector = resultsData->cellScalarResults( resultAddress, timeStepIdx );
namedInputVector[resultName] = &resultVector;
}
std::map<QString, std::vector<double>> namedOutputVector;
for ( const auto& [resultName, vec] : resultNamesAndSamples )
{
namedOutputVector[resultName] = std::vector( targetNumActiveCells, std::numeric_limits<double>::infinity() );
}
for ( ReservoirCellIndex targetCellIdx( 0 ); targetCellIdx.value() < targetNumReservoirCells; ++targetCellIdx )
{
const RigCell& nativeCell = targetCase.mainGrid()->cell( targetCellIdx.value() );
cvf::Vec3d cellCenter = nativeCell.center();
ActiveCellIndex targetResultIndex = targetActiveCellInfo->cellResultIndex( targetCellIdx );
ReservoirCellIndex cellIdx( mainGrid->findReservoirCellIndexFromPoint( cellCenter ) );
if ( cellIdx.value() != cvf::UNDEFINED_SIZE_T && activeCellInfo->isActive( cellIdx ) &&
targetResultIndex.value() != cvf::UNDEFINED_SIZE_T )
{
ActiveCellIndex resultIndex = resultsData->activeCellInfo()->cellResultIndex( cellIdx );
if ( !std::isinf( clusterNum[resultIndex.value()] ) && clusterNum[resultIndex.value()] > 0 )
{
occupancy[targetResultIndex.value()]++;
for ( const auto& [resultName, vec] : resultNamesAndSamples )
{
namedOutputVector[resultName][targetResultIndex.value()] = namedInputVector[resultName]->at( resultIndex.value() );
}
}
}
}
for ( const auto& [resultName, vec] : resultNamesAndSamples )
{
resultNamesAndSamples[resultName].push_back( namedOutputVector[resultName] );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::BoundingBox
RigWellTargetMappingTools::computeBoundingBoxForResult( RimEclipseCase& eclipseCase, const QString& resultName, size_t timeStepIndex )
{
RigCaseCellResultsData* resultsData = eclipseCase.results( RiaDefines::PorosityModelType::MATRIX_MODEL );
const RigMainGrid* mainGrid = eclipseCase.mainGrid();
const RigActiveCellInfo* activeCellInfo = resultsData->activeCellInfo();
const size_t numReservoirCells = activeCellInfo->reservoirCellCount();
RigEclipseResultAddress clustersNumAddress( RiaDefines::ResultCatType::GENERATED, resultName );
resultsData->ensureKnownResultLoaded( clustersNumAddress );
const std::vector<double>& clusterNum = resultsData->cellScalarResults( clustersNumAddress, timeStepIndex );
cvf::BoundingBox boundingBox;
for ( ReservoirCellIndex reservoirCellIdx( 0 ); reservoirCellIdx.value() < numReservoirCells; ++reservoirCellIdx )
{
ActiveCellIndex targetResultIndex = activeCellInfo->cellResultIndex( reservoirCellIdx );
if ( activeCellInfo->isActive( reservoirCellIdx ) && targetResultIndex.value() != cvf::UNDEFINED_SIZE_T &&
!std::isinf( clusterNum[targetResultIndex.value()] ) && clusterNum[targetResultIndex.value()] > 0 )
{
const RigCell& nativeCell = mainGrid->cell( reservoirCellIdx.value() );
boundingBox.add( nativeCell.boundingBox() );
}
}
return boundingBox;
}
@@ -0,0 +1,207 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "RigActiveCellInfo.h"
#include "RigEclipseResultAddress.h"
#include "RigWellTargetMapping.h"
#include "cafVecIjk.h"
#include "cvfBoundingBox.h"
#include "cvfStructGrid.h"
#include <list>
#include <map>
#include <optional>
#include <utility>
#include <vector>
class RigCaseCellResultsData;
class RigFloodingSettings;
class RigMainGrid;
class RimEclipseCase;
//==================================================================================================
///
///
//==================================================================================================
class RigWellTargetMappingTools
{
public:
using CellFaceType = cvf::StructGridInterface::FaceType;
using VolumeType = RigWellTargetMapping::VolumeType;
using VolumesType = RigWellTargetMapping::VolumesType;
using VolumeResultType = RigWellTargetMapping::VolumeResultType;
using ClusteringLimits = RigWellTargetMapping::ClusteringLimits;
struct DataContainer
{
std::vector<double> volume;
std::vector<double> saturationOil;
std::vector<double> saturationGas;
std::vector<double> pressure;
std::vector<double> permeabilityX;
std::vector<double> permeabilityNNC;
std::vector<double> transmissibilityX;
std::vector<double> transmissibilityY;
std::vector<double> transmissibilityZ;
const std::vector<double>* transmissibilityNNC;
};
class ClusterStatistics
{
public:
ClusterStatistics()
: id( -1 )
, numCells( 0 )
, totalPorvSoil( 0.0 )
, totalPorvSgas( 0.0 )
, totalPorvSoilAndSgas( 0.0 )
, totalFipOil( 0.0 )
, totalFipGas( 0.0 )
, totalRfipOil( 0.0 )
, totalRfipGas( 0.0 )
, totalSfipOil( 0.0 )
, totalSfipGas( 0.0 )
, permeability( 0.0 )
, pressure( 0.0 )
{
}
int id;
size_t numCells;
double totalPorvSoil;
double totalPorvSgas;
double totalPorvSoilAndSgas;
double totalFipOil;
double totalFipGas;
double totalRfipOil;
double totalRfipGas;
double totalSfipOil;
double totalSfipGas;
double permeability;
double pressure;
};
static double getValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
CellFaceType face,
ActiveCellIndex resultIndex );
static double getTransmissibilityValueForFace( const std::vector<double>& x,
const std::vector<double>& y,
const std::vector<double>& z,
CellFaceType face,
ActiveCellIndex resultIndex,
ActiveCellIndex neighborResultIndex );
static QString getOilVectorName( VolumesType volumesType );
static QString getGasVectorName( VolumesType volumesType );
static bool isSaturationSufficient( VolumeType volumeType, const DataContainer& data, const ClusteringLimits& limits, ActiveCellIndex idx );
static void assignClusterIdToCells( const RigActiveCellInfo& activeCellInfo,
const std::vector<ReservoirCellIndex>& cells,
std::vector<int>& clusters,
int clusterId );
static std::list<std::pair<std::pair<ReservoirCellIndex, CellFaceType>, size_t>> nncConnectionCellAndResult( ReservoirCellIndex cellIdx,
RigMainGrid* mainGrid );
static void createDynamicResultEntry( RigCaseCellResultsData* resultsData, const RigEclipseResultAddress& address );
static void
createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<int>& clusterIds, size_t timeStepIdx );
static void
createResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<double>& values, size_t timeStepIdx );
static void createStaticResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<int>& intValues );
static void createStaticResultVector( RimEclipseCase& eclipseCase, const QString& resultName, const std::vector<double>& values );
static void createResultVectorIfDefined( RimEclipseCase& eclipseCase,
const QString& resultName,
const std::vector<double>& values,
int timeStepIdx = -1 );
static std::optional<caf::VecIjk0> findStartCell( RimEclipseCase* eclipseCase,
size_t timeStepIdx,
VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
const std::vector<int>& clusters );
static void growCluster( RimEclipseCase* eclipseCase,
const caf::VecIjk0& startCell,
VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
std::vector<int>& clusters,
int clusterId,
size_t timeStepIdx,
int maxIterations );
static std::vector<ReservoirCellIndex> findCandidates( RimEclipseCase* eclipseCase,
const std::vector<ReservoirCellIndex>& previousCells,
VolumeType volumeType,
const ClusteringLimits& limits,
const DataContainer& data,
const std::vector<double>& filterVector,
std::vector<int>& clusters );
static std::optional<size_t> getActiveCellCount( RimEclipseCase* eclipseCase );
static std::vector<ClusterStatistics> generateStatistics( RimEclipseCase* eclipseCase,
const std::vector<double>& pressure,
const std::vector<double>& permeabilityX,
int numClustersFound,
size_t timeStepIdx,
const QString& clusterResultName );
static void computeStatisticsAndCreateVectors( RimEclipseCase& targetCase,
const QString& resultName,
const std::vector<std::vector<double>>& vec );
static void accumulateResultsForSingleCase( RimEclipseCase& eclipseCase,
RimEclipseCase& targetCase,
std::map<QString, std::vector<std::vector<double>>>& resultNamesAndSamples,
std::vector<int>& occupancy,
size_t timeStepIdx );
static cvf::BoundingBox computeBoundingBoxForResult( RimEclipseCase& eclipseCase, const QString& resultName, size_t timeStepIndex );
static std::vector<double> loadVectorByName( RigCaseCellResultsData& resultsData, const QString& resultName, size_t timeStepIdx );
static std::vector<double> loadOilVectorByName( RigCaseCellResultsData& resultsData,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings );
static std::vector<double> loadGasVectorByName( RigCaseCellResultsData& resultsData,
RiaDefines::EclipseUnitSystem unitsType,
VolumesType volumesType,
VolumeResultType volumeResultType,
size_t timeStepIdx,
const RigFloodingSettings& floodingSettings );
};
@@ -132,6 +132,7 @@ set(SOURCE_UNITTEST_FILES
${CMAKE_CURRENT_LIST_DIR}/RimMockSummaryCase-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RimSummaryCalculation-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaConnectorTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RigWellTargetMappingTools-Test.cpp
)
if(RESINSIGHT_ENABLE_GRPC)
@@ -0,0 +1,636 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "gtest/gtest.h"
#include "Well/RigWellTargetMappingTools.h"
#include "RiaResultNames.h"
#include "RigActiveCellInfo.h"
#include "RigMainGrid.h"
#include "RigNNCData.h"
#include "RigNncConnection.h"
#include "cvfStructGrid.h"
#include <limits>
#include <vector>
using FaceType = cvf::StructGridInterface::FaceType;
using VolumeType = RigWellTargetMapping::VolumeType;
using VolumesType = RigWellTargetMapping::VolumesType;
//==================================================================================================
// getValueForFace
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, GetValueForFace_PosI_ReturnsXAtIndex )
{
std::vector<double> x = { 1.0, 2.0, 3.0 };
std::vector<double> y = { 10.0, 20.0, 30.0 };
std::vector<double> z = { 100.0, 200.0, 300.0 };
EXPECT_DOUBLE_EQ( 2.0, RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::POS_I, ActiveCellIndex( 1 ) ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_NegI_ReturnsXAtIndex )
{
std::vector<double> x = { 1.0, 2.0, 3.0 };
std::vector<double> y = { 10.0, 20.0, 30.0 };
std::vector<double> z = { 100.0, 200.0, 300.0 };
EXPECT_DOUBLE_EQ( 1.0, RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::NEG_I, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_PosJ_ReturnsYAtIndex )
{
std::vector<double> x = { 1.0, 2.0, 3.0 };
std::vector<double> y = { 10.0, 20.0, 30.0 };
std::vector<double> z = { 100.0, 200.0, 300.0 };
EXPECT_DOUBLE_EQ( 30.0, RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::POS_J, ActiveCellIndex( 2 ) ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_NegJ_ReturnsYAtIndex )
{
std::vector<double> x = { 1.0, 2.0, 3.0 };
std::vector<double> y = { 10.0, 20.0, 30.0 };
std::vector<double> z = { 100.0, 200.0, 300.0 };
EXPECT_DOUBLE_EQ( 20.0, RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::NEG_J, ActiveCellIndex( 1 ) ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_PosK_ReturnsZAtIndex )
{
std::vector<double> x = { 1.0, 2.0, 3.0 };
std::vector<double> y = { 10.0, 20.0, 30.0 };
std::vector<double> z = { 100.0, 200.0, 300.0 };
EXPECT_DOUBLE_EQ( 100.0, RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::POS_K, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_NegK_ReturnsZAtIndex )
{
std::vector<double> x = { 1.0, 2.0, 3.0 };
std::vector<double> y = { 10.0, 20.0, 30.0 };
std::vector<double> z = { 100.0, 200.0, 300.0 };
EXPECT_DOUBLE_EQ( 300.0, RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::NEG_K, ActiveCellIndex( 2 ) ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_NoFace_ReturnsInfinity )
{
std::vector<double> x = { 1.0 };
std::vector<double> y = { 2.0 };
std::vector<double> z = { 3.0 };
double result = RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::NO_FACE, ActiveCellIndex( 0 ) );
EXPECT_TRUE( std::isinf( result ) );
}
TEST( RigWellTargetMappingToolsTest, GetValueForFace_PosI_NegI_ReturnSameXValue )
{
// Both I-directions should return from the same x vector
std::vector<double> x = { 5.0 };
std::vector<double> y = { 50.0 };
std::vector<double> z = { 500.0 };
double posI = RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::POS_I, ActiveCellIndex( 0 ) );
double negI = RigWellTargetMappingTools::getValueForFace( x, y, z, FaceType::NEG_I, ActiveCellIndex( 0 ) );
EXPECT_DOUBLE_EQ( posI, negI );
}
//==================================================================================================
// getTransmissibilityValueForFace
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_PosI_UsesCellIndex )
{
std::vector<double> x = { 5.0, 15.0, 25.0 };
std::vector<double> y = { 6.0, 16.0, 26.0 };
std::vector<double> z = { 7.0, 17.0, 27.0 };
double result =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::POS_I, ActiveCellIndex( 0 ), ActiveCellIndex( 2 ) );
EXPECT_DOUBLE_EQ( 5.0, result ); // x[0]
}
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_NegI_UsesNeighborIndex )
{
std::vector<double> x = { 5.0, 15.0, 25.0 };
std::vector<double> y = { 6.0, 16.0, 26.0 };
std::vector<double> z = { 7.0, 17.0, 27.0 };
double result =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::NEG_I, ActiveCellIndex( 0 ), ActiveCellIndex( 2 ) );
EXPECT_DOUBLE_EQ( 25.0, result ); // x[2] (neighbor)
}
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_PosJ_UsesCellIndex )
{
std::vector<double> x = { 5.0, 15.0 };
std::vector<double> y = { 6.0, 16.0 };
std::vector<double> z = { 7.0, 17.0 };
double result =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::POS_J, ActiveCellIndex( 0 ), ActiveCellIndex( 1 ) );
EXPECT_DOUBLE_EQ( 6.0, result ); // y[0]
}
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_NegJ_UsesNeighborIndex )
{
std::vector<double> x = { 5.0, 15.0 };
std::vector<double> y = { 6.0, 16.0 };
std::vector<double> z = { 7.0, 17.0 };
double result =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::NEG_J, ActiveCellIndex( 0 ), ActiveCellIndex( 1 ) );
EXPECT_DOUBLE_EQ( 16.0, result ); // y[1] (neighbor)
}
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_PosK_UsesCellIndex )
{
std::vector<double> x = { 5.0, 15.0 };
std::vector<double> y = { 6.0, 16.0 };
std::vector<double> z = { 7.0, 17.0 };
double result =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::POS_K, ActiveCellIndex( 0 ), ActiveCellIndex( 1 ) );
EXPECT_DOUBLE_EQ( 7.0, result ); // z[0]
}
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_NegK_UsesNeighborIndex )
{
std::vector<double> x = { 5.0, 15.0 };
std::vector<double> y = { 6.0, 16.0 };
std::vector<double> z = { 7.0, 17.0 };
double result =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::NEG_K, ActiveCellIndex( 0 ), ActiveCellIndex( 1 ) );
EXPECT_DOUBLE_EQ( 17.0, result ); // z[1] (neighbor)
}
TEST( RigWellTargetMappingToolsTest, GetTransmissibilityValueForFace_SameIndex_SameValueForAllDirections )
{
// When cell and neighbor share the same result index, direction does not matter
std::vector<double> x = { 9.0 };
std::vector<double> y = { 19.0 };
std::vector<double> z = { 29.0 };
double posI =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::POS_I, ActiveCellIndex( 0 ), ActiveCellIndex( 0 ) );
double negI =
RigWellTargetMappingTools::getTransmissibilityValueForFace( x, y, z, FaceType::NEG_I, ActiveCellIndex( 0 ), ActiveCellIndex( 0 ) );
EXPECT_DOUBLE_EQ( posI, negI );
}
//==================================================================================================
// getOilVectorName
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, GetOilVectorName_ReservoirVolumes )
{
EXPECT_EQ( QString( "RFIPOIL" ), RigWellTargetMappingTools::getOilVectorName( VolumesType::RESERVOIR_VOLUMES ) );
}
TEST( RigWellTargetMappingToolsTest, GetOilVectorName_SurfaceVolumesSfip )
{
EXPECT_EQ( QString( "SFIPOIL" ), RigWellTargetMappingTools::getOilVectorName( VolumesType::SURFACE_VOLUMES_SFIP ) );
}
TEST( RigWellTargetMappingToolsTest, GetOilVectorName_SurfaceVolumesFip )
{
EXPECT_EQ( QString( "FIPOIL" ), RigWellTargetMappingTools::getOilVectorName( VolumesType::SURFACE_VOLUMES_FIP ) );
}
TEST( RigWellTargetMappingToolsTest, GetOilVectorName_ReservoirVolumesComputed )
{
EXPECT_EQ( RiaResultNames::riPorvSoil(), RigWellTargetMappingTools::getOilVectorName( VolumesType::RESERVOIR_VOLUMES_COMPUTED ) );
}
//==================================================================================================
// getGasVectorName
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, GetGasVectorName_ReservoirVolumes )
{
EXPECT_EQ( QString( "RFIPGAS" ), RigWellTargetMappingTools::getGasVectorName( VolumesType::RESERVOIR_VOLUMES ) );
}
TEST( RigWellTargetMappingToolsTest, GetGasVectorName_SurfaceVolumesSfip )
{
EXPECT_EQ( QString( "SFIPGAS" ), RigWellTargetMappingTools::getGasVectorName( VolumesType::SURFACE_VOLUMES_SFIP ) );
}
TEST( RigWellTargetMappingToolsTest, GetGasVectorName_SurfaceVolumesFip )
{
EXPECT_EQ( QString( "FIPGAS" ), RigWellTargetMappingTools::getGasVectorName( VolumesType::SURFACE_VOLUMES_FIP ) );
}
TEST( RigWellTargetMappingToolsTest, GetGasVectorName_ReservoirVolumesComputed )
{
EXPECT_EQ( RiaResultNames::riPorvSgas(), RigWellTargetMappingTools::getGasVectorName( VolumesType::RESERVOIR_VOLUMES_COMPUTED ) );
}
//==================================================================================================
// isSaturationSufficient
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_OilAboveThreshold_ReturnsTrue )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.3 };
data.saturationGas = { 0.0 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.1;
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_OilBelowThreshold_ReturnsFalse )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.1 };
data.saturationGas = { 0.0 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.1;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 1 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_OilExactlyAtThreshold_ReturnsTrue )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.2 };
data.saturationGas = { 0.0 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.1;
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_GasAboveThreshold_ReturnsTrue )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.0 };
data.saturationGas = { 0.5 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::GAS, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_GasBelowThreshold_ReturnsFalse )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.0 };
data.saturationGas = { 0.1 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::GAS, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_GasExactlyAtThreshold_ReturnsTrue )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.0 };
data.saturationGas = { 0.3 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::GAS, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_Hydrocarbon_OilSufficient_ReturnsTrue )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.4 };
data.saturationGas = { 0.05 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::HYDROCARBON, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_Hydrocarbon_GasSufficient_ReturnsTrue )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.05 };
data.saturationGas = { 0.5 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::HYDROCARBON, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_Hydrocarbon_NeitherSufficient_ReturnsFalse )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.05 };
data.saturationGas = { 0.05 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::HYDROCARBON, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_OilType_HighGasIsIgnored_ReturnsFalse )
{
// VolumeType::OIL only checks oil saturation — high gas must not rescue it
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.05 };
data.saturationGas = { 0.9 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_GasType_HighOilIsIgnored_ReturnsFalse )
{
// VolumeType::GAS only checks gas saturation — high oil must not rescue it
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.9 };
data.saturationGas = { 0.05 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.3;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::GAS, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_ZeroSaturation_ReturnsFalse )
{
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.0 };
data.saturationGas = { 0.0 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.01;
limits.saturationGas = 0.01;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 0 ) ) );
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::GAS, data, limits, ActiveCellIndex( 0 ) ) );
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::HYDROCARBON, data, limits, ActiveCellIndex( 0 ) ) );
}
TEST( RigWellTargetMappingToolsTest, IsSaturationSufficient_SecondIndex_UsesCorrectElement )
{
// Verify the index selects the right element from the vectors
RigWellTargetMappingTools::DataContainer data;
data.saturationOil = { 0.05, 0.5 }; // index 0 is below limit, index 1 is above
data.saturationGas = { 0.05, 0.05 };
RigWellTargetMapping::ClusteringLimits limits{};
limits.saturationOil = 0.2;
limits.saturationGas = 0.2;
EXPECT_FALSE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 0 ) ) );
EXPECT_TRUE( RigWellTargetMappingTools::isSaturationSufficient( VolumeType::OIL, data, limits, ActiveCellIndex( 1 ) ) );
}
//==================================================================================================
// assignClusterIdToCells
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, AssignClusterIdToCells_AllActiveCells_SetsCorrectIds )
{
RigActiveCellInfo activeCellInfo;
activeCellInfo.setReservoirCellCount( 3 );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 0 ), ActiveCellIndex( 0 ) );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 1 ), ActiveCellIndex( 1 ) );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 2 ), ActiveCellIndex( 2 ) );
std::vector<int> clusters( 3, 0 );
std::vector<ReservoirCellIndex> cells = { ReservoirCellIndex( 0 ), ReservoirCellIndex( 1 ), ReservoirCellIndex( 2 ) };
RigWellTargetMappingTools::assignClusterIdToCells( activeCellInfo, cells, clusters, 5 );
EXPECT_EQ( 5, clusters[0] );
EXPECT_EQ( 5, clusters[1] );
EXPECT_EQ( 5, clusters[2] );
}
TEST( RigWellTargetMappingToolsTest, AssignClusterIdToCells_InactiveCell_IsSkipped )
{
RigActiveCellInfo activeCellInfo;
activeCellInfo.setReservoirCellCount( 3 );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 0 ), ActiveCellIndex( 0 ) );
// reservoir cells 1 and 2 have no result index (inactive)
std::vector<int> clusters( 1, 0 );
std::vector<ReservoirCellIndex> cells = { ReservoirCellIndex( 0 ), ReservoirCellIndex( 1 ), ReservoirCellIndex( 2 ) };
RigWellTargetMappingTools::assignClusterIdToCells( activeCellInfo, cells, clusters, 7 );
EXPECT_EQ( 7, clusters[0] ); // cell 0 is active → updated
}
TEST( RigWellTargetMappingToolsTest, AssignClusterIdToCells_EmptyCells_ClustersUnchanged )
{
RigActiveCellInfo activeCellInfo;
activeCellInfo.setReservoirCellCount( 2 );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 0 ), ActiveCellIndex( 0 ) );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 1 ), ActiveCellIndex( 1 ) );
std::vector<int> clusters( 2, 99 );
std::vector<ReservoirCellIndex> cells;
RigWellTargetMappingTools::assignClusterIdToCells( activeCellInfo, cells, clusters, 1 );
EXPECT_EQ( 99, clusters[0] );
EXPECT_EQ( 99, clusters[1] );
}
TEST( RigWellTargetMappingToolsTest, AssignClusterIdToCells_OverwritesExistingClusterId )
{
RigActiveCellInfo activeCellInfo;
activeCellInfo.setReservoirCellCount( 2 );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 0 ), ActiveCellIndex( 0 ) );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 1 ), ActiveCellIndex( 1 ) );
std::vector<int> clusters = { 3, 3 };
std::vector<ReservoirCellIndex> cells = { ReservoirCellIndex( 0 ), ReservoirCellIndex( 1 ) };
RigWellTargetMappingTools::assignClusterIdToCells( activeCellInfo, cells, clusters, 9 );
EXPECT_EQ( 9, clusters[0] );
EXPECT_EQ( 9, clusters[1] );
}
TEST( RigWellTargetMappingToolsTest, AssignClusterIdToCells_MixedActiveInactive_OnlyActiveUpdated )
{
RigActiveCellInfo activeCellInfo;
activeCellInfo.setReservoirCellCount( 4 );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 0 ), ActiveCellIndex( 0 ) );
// cell 1 inactive
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 2 ), ActiveCellIndex( 1 ) );
// cell 3 inactive
std::vector<int> clusters( 2, 0 );
std::vector<ReservoirCellIndex> cells = { ReservoirCellIndex( 0 ), ReservoirCellIndex( 1 ), ReservoirCellIndex( 2 ), ReservoirCellIndex( 3 ) };
RigWellTargetMappingTools::assignClusterIdToCells( activeCellInfo, cells, clusters, 4 );
EXPECT_EQ( 4, clusters[0] ); // result index 0 → updated
EXPECT_EQ( 4, clusters[1] ); // result index 1 → updated
}
TEST( RigWellTargetMappingToolsTest, AssignClusterIdToCells_SingleCell_SetsId )
{
RigActiveCellInfo activeCellInfo;
activeCellInfo.setReservoirCellCount( 1 );
activeCellInfo.setCellResultIndex( ReservoirCellIndex( 0 ), ActiveCellIndex( 0 ) );
std::vector<int> clusters( 1, 0 );
std::vector<ReservoirCellIndex> cells = { ReservoirCellIndex( 0 ) };
RigWellTargetMappingTools::assignClusterIdToCells( activeCellInfo, cells, clusters, 42 );
EXPECT_EQ( 42, clusters[0] );
}
//==================================================================================================
// nncConnectionCellAndResult
//==================================================================================================
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_NoConnections_ReturnsEmpty )
{
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 0 ), mainGrid.p() );
EXPECT_TRUE( result.empty() );
}
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_MatchingCell_ReturnsConnection )
{
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)3, (size_t)7, FaceType::POS_I ) );
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 3 ), mainGrid.p() );
ASSERT_EQ( 1u, result.size() );
auto& [cellInfo, nncIdx] = result.front();
EXPECT_EQ( 7u, cellInfo.first.value() );
EXPECT_EQ( FaceType::POS_I, cellInfo.second );
EXPECT_EQ( 0u, nncIdx );
}
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_NonMatchingCell_ReturnsEmpty )
{
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)5, (size_t)8, FaceType::NEG_J ) );
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 0 ), mainGrid.p() );
EXPECT_TRUE( result.empty() );
}
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_CellIsC2NotC1_NotReturned )
{
// The function only matches on c1GlobIdx, not c2GlobIdx.
// RigConnection always stores the smaller index as c1, so we need c1 < 3 to ensure cell 3 is stored as c2.
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)1, (size_t)3, FaceType::POS_J ) );
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 3 ), mainGrid.p() );
EXPECT_TRUE( result.empty() );
}
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_MultipleConnections_ReturnsOnlyMatching )
{
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)2, (size_t)10, FaceType::POS_I ) );
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)4, (size_t)11, FaceType::POS_J ) ); // different source
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)2, (size_t)12, FaceType::NEG_K ) );
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 2 ), mainGrid.p() );
ASSERT_EQ( 2u, result.size() );
auto it = result.begin();
EXPECT_EQ( 10u, it->first.first.value() );
EXPECT_EQ( 0u, it->second ); // position 0 in connections array
++it;
EXPECT_EQ( 12u, it->first.first.value() );
EXPECT_EQ( 2u, it->second ); // position 2 in connections array
}
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_NncIndexReflectsPositionInArray )
{
// NNC index must be the position in the global connections array, not the position in the result list.
// RigConnection always stores the smaller index as c1, so cell 7 must be smaller than its neighbor to be c1.
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)1, (size_t)9, FaceType::POS_I ) ); // idx 0 — no match (c1=1)
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)2, (size_t)9, FaceType::POS_J ) ); // idx 1 — no match (c1=2)
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)7, (size_t)11, FaceType::NEG_I ) ); // idx 2 — match (c1=7)
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)7, (size_t)12, FaceType::NEG_J ) ); // idx 3 — match (c1=7)
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 7 ), mainGrid.p() );
ASSERT_EQ( 2u, result.size() );
auto it = result.begin();
EXPECT_EQ( 2u, it->second ); // global array index 2
++it;
EXPECT_EQ( 3u, it->second ); // global array index 3
}
TEST( RigWellTargetMappingToolsTest, NncConnectionCellAndResult_StoredFaceTypeIsPreserved )
{
cvf::ref<RigMainGrid> mainGrid = new RigMainGrid;
mainGrid->nncData()->allConnections().push_back( RigConnection( (size_t)1, (size_t)2, FaceType::NEG_K ) );
auto result = RigWellTargetMappingTools::nncConnectionCellAndResult( ReservoirCellIndex( 1 ), mainGrid.p() );
ASSERT_EQ( 1u, result.size() );
EXPECT_EQ( FaceType::NEG_K, result.front().first.second );
}