Well target mapping: separate start and grow data filters

Add a start data filter used when finding starting cells. If empty, the
grow filter is used. An empty grow filter allows growing anywhere.
This commit is contained in:
Kristian Bendiksen committed 2026-10-15 12:56:18 +02:00
1 parent da4e6e15cd
commit 19f94395a7
5 files changed
+76 -41

No files matched your search

@@ -160,7 +160,17 @@ RimWellTargetMapping::RimWellTargetMapping()
"How much to increase the bounding box of the first case to cover for any grid size differences across the "
"ensemble." );
CAF_PDM_InitFieldNoDefault( &m_dataFilter, "DataFilter", "Data Filter" );
// "DataFilter" is the grow filter; keeping the keyword keeps old project files working (an empty start filter falls back to it).
CAF_PDM_InitFieldNoDefault( &m_startDataFilter,
"StartDataFilter",
"Start Data Filter",
"",
"Limits where starting cells are searched for. If empty, the grow data filter is used." );
CAF_PDM_InitFieldNoDefault( &m_dataFilter,
"DataFilter",
"Grow Data Filter",
"",
"Limits where clusters can grow. If empty, clusters can grow anywhere in the model." );
CAF_PDM_InitFieldNoDefault( &m_ensembleStatisticsCase, "EnsembleStatisticsCase", "Ensemble Statistics Case" );
@@ -226,7 +236,7 @@ QList<caf::PdmOptionItemInfo> RimWellTargetMapping::calculateValueOptions( const
caf::AppEnum<RigWellTargetMapping::VolumesType>::setEnumSubset( &m_volumesType, findAvailableVolumesTypes( fc ) );
}
}
else if ( fieldNeedingOptions == &m_dataFilter )
else if ( fieldNeedingOptions == &m_dataFilter || fieldNeedingOptions == &m_startDataFilter )
{
options.push_back( caf::PdmOptionItemInfo( "None", nullptr ) );
@@ -498,6 +508,7 @@ void RimWellTargetMapping::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd
auto gridEnsemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>();
auto hasEnsembleParent = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>() != nullptr || gridEnsemble != nullptr;
uiOrdering.add( &m_startDataFilter );
uiOrdering.add( &m_dataFilter );
caf::PdmUiGroup* minimumCellValuesGroup = uiOrdering.addNewGroup( "Minimum Cell Values" );
@@ -560,7 +571,8 @@ RigWellTargetMapping::ClusteringLimits RimWellTargetMapping::getClusteringLimits
.maxNumTargets = m_maxNumTargets,
.maxIterations = m_maxIterations,
.filterAddress = m_resultDefinition->eclipseResultAddress(),
.dataFilter = m_dataFilter() };
.startDataFilter = m_startDataFilter(),
.growDataFilter = m_dataFilter() };
}
//--------------------------------------------------------------------------------------------------
@@ -107,7 +107,8 @@ private:
caf::PdmField<int> m_cellCountJ;
caf::PdmField<int> m_cellCountK;
caf::PdmPtrField<RimCellFilter*> m_dataFilter;
caf::PdmPtrField<RimCellFilter*> m_startDataFilter;
caf::PdmPtrField<RimCellFilter*> m_dataFilter; // grow filter
caf::PdmChildField<RimEclipseCase*> m_ensembleStatisticsCase;
@@ -146,45 +146,53 @@ void RigWellTargetMapping::generateCandidates( RimEclipseCase* eclips
data.transmissibilityNNC = nullptr;
}
std::vector<double> filterVector;
if ( limits.dataFilter )
// Evaluate for each realization after opening its grid, using the mapping's time step.
auto computeFilterVector = [&]( RimCellFilter* dataFilter ) -> std::vector<double>
{
// Evaluate for each realization after opening its grid, using the mapping's time step.
auto visibility = RimCellFilterTools::computeReservoirCellVisibility( limits.dataFilter, eclipseCase, timeStepIdx );
if ( visibility.notNull() )
std::vector<double> filterVector;
if ( dataFilter )
{
auto activeReservoirCellIndices =
caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices();
const int numActiveCells = static_cast<int>( activeReservoirCellIndices.size() );
filterVector.resize( numActiveCells );
// Convert reservoir visibility to the active-cell indexing used by clustering.
#pragma omp parallel for
for ( int i = 0; i < numActiveCells; i++ )
auto visibility = RimCellFilterTools::computeReservoirCellVisibility( dataFilter, eclipseCase, timeStepIdx );
if ( visibility.notNull() )
{
filterVector[i] = visibility->val( activeReservoirCellIndices[i].value() ) ? 1.0 : 0.0;
auto activeReservoirCellIndices =
caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL )->activeReservoirCellIndices();
const int numActiveCells = static_cast<int>( activeReservoirCellIndices.size() );
filterVector.resize( numActiveCells );
// Convert reservoir visibility to the active-cell indexing used by clustering.
#pragma omp parallel for
for ( int i = 0; i < numActiveCells; i++ )
{
filterVector[i] = visibility->val( activeReservoirCellIndices[i].value() ) ? 1.0 : 0.0;
}
}
else
{
// Fall through to the unfiltered path below so all realizations in an ensemble get a result,
// consistent with how an invalid filterAddress is handled.
RiaLogging::warning(
std::format( "Unable to evaluate well target mapping data filter for case '{}'. Continuing without filter.",
eclipseCase->caseUserDescription().toStdString() ) );
}
}
else
else if ( limits.filterAddress.isValid() )
{
// Fall through to the unfiltered path below so all realizations in an ensemble get a result,
// consistent with how an invalid filterAddress is handled.
RiaLogging::warning(
std::format( "Unable to evaluate well target mapping data filter for case '{}'. Continuing without filter.",
eclipseCase->caseUserDescription().toStdString() ) );
resultsData->ensureKnownResultLoaded( limits.filterAddress );
filterVector = resultsData->cellScalarResults( limits.filterAddress, timeStepIdx );
}
}
else if ( limits.filterAddress.isValid() )
{
resultsData->ensureKnownResultLoaded( limits.filterAddress );
filterVector = resultsData->cellScalarResults( limits.filterAddress, timeStepIdx );
}
// Disable filtering if not configured: set all items to 1 (which includes all cells).
if ( filterVector.empty() || filterVector.size() != data.pressure.size() )
{
filterVector = std::vector<double>( data.pressure.size(), 1.0 );
}
// Disable filtering if not configured: set all items to 1 (which includes all cells).
if ( filterVector.empty() || filterVector.size() != data.pressure.size() )
{
filterVector = std::vector<double>( data.pressure.size(), 1.0 );
}
return filterVector;
};
const std::vector<double> growFilterVector = computeFilterVector( limits.growDataFilter );
// Without a start filter, the grow filter is used to find the starting cells.
const std::vector<double> startFilterVector = limits.startDataFilter ? computeFilterVector( limits.startDataFilter ) : growFilterVector;
const QString logKeyword = "RigWellTargetMapping";
@@ -196,7 +204,7 @@ void RigWellTargetMapping::generateCandidates( RimEclipseCase* eclips
for ( int clusterId = 1; clusterId <= numClusters; clusterId++ )
{
std::optional<caf::VecIjk0> startCell =
RigWellTargetMappingTools::findStartCell( eclipseCase, timeStepIdx, volumeType, limits, data, filterVector, clusters );
RigWellTargetMappingTools::findStartCell( eclipseCase, timeStepIdx, volumeType, limits, data, startFilterVector, clusters );
if ( startCell.has_value() )
{
@@ -213,7 +221,7 @@ void RigWellTargetMapping::generateCandidates( RimEclipseCase* eclips
volumeType,
limits,
data,
filterVector,
growFilterVector,
clusters,
clusterId,
timeStepIdx,
@@ -68,7 +68,8 @@ public:
int maxNumTargets;
int maxIterations;
RigEclipseResultAddress filterAddress;
RimCellFilter* dataFilter = nullptr;
RimCellFilter* startDataFilter = nullptr; // falls back to growDataFilter if null
RimCellFilter* growDataFilter = nullptr;
};
static void generateCandidates( RimEclipseCase* eclipseCase,
@@ -113,6 +113,19 @@ TEST( RimWellTargetMappingTest, FilterOptionsUseOwningCase )
EXPECT_EQ( caf::PdmOptionItemInfo( "Case filter", filter ).value(), options[1].value() );
}
TEST( RimWellTargetMappingTest, StartFilterOptionsUseOwningCase )
{
RimEclipseResultCase eclipseCase;
auto mapping = new TestableWellTargetMapping;
eclipseCase.addWellTargetMapping( mapping );
auto filter = eclipseCase.dataFilterCollection()->addNewRangeFilter();
filter->setName( "Case filter" );
auto options = mapping->calculateValueOptions( mapping->findField( "StartDataFilter" ) );
ASSERT_EQ( 2, options.size() );
EXPECT_EQ( caf::PdmOptionItemInfo( "Case filter", filter ).value(), options[1].value() );
}
TEST( RimWellTargetMappingTest, FilterOptionsUseOwningEnsemble )
{
RimReservoirGridEnsemble ensemble;
@@ -222,9 +235,9 @@ TEST_P( RimWellTargetMappingFilterTest, FiltersClustersWithoutViews )
if ( useEnsemble )
{
RigWellTargetMapping::ClusteringLimits limits{};
limits.maxNumTargets = 1;
limits.maxIterations = 10;
limits.dataFilter = filter;
limits.maxNumTargets = 1;
limits.maxIterations = 10;
limits.growDataFilter = filter;
RigFloodingSettings floodingSettings( RigFloodingSettings::FloodingType::WATER_FLOODING,
0.0,
RigFloodingSettings::FloodingType::GAS_FLOODING,