Add realization filtering based on text string

Add unit tests and support min max range specifications. Example string "-5, 5, 8-10, 12-"
This commit is contained in:
Magne Sjaastad 2024-03-21 06:46:19 +01:00
parent 691b73d7f4
commit 785871cae3
8 changed files with 245 additions and 4 deletions

View File

@ -157,3 +157,11 @@ QString RiaDefines::summaryCalculated()
{
return "Calculated";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaDefines::summaryRealizationNumber()
{
return "RI:REALIZATION_NUM";
}

View File

@ -52,4 +52,6 @@ QString summaryLgrWell();
QString summaryLgrBlock();
QString summaryCalculated();
QString summaryRealizationNumber();
}; // namespace RiaDefines

View File

@ -17,9 +17,12 @@
/////////////////////////////////////////////////////////////////////////////////
#include "RiaStdStringTools.h"
#include "RiaLogging.h"
#include "fast_float/include/fast_float/fast_float.h"
#include <QString>
#include <charconv>
#include <regex>
#include <sstream>
@ -301,3 +304,118 @@ std::string RiaStdStringTools::removeHtmlTags( const std::string& s )
return result;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s )
{
try
{
std::set<int> result;
std::istringstream stringStream( s );
std::string token;
while ( std::getline( stringStream, token, ',' ) )
{
token = RiaStdStringTools::trimString( token );
std::istringstream tokenStream( token );
int startIndex, endIndex;
char dash;
if ( tokenStream >> startIndex )
{
if ( tokenStream >> dash && dash == '-' && tokenStream >> endIndex )
{
if ( startIndex > endIndex )
{
// If start is greater than end, swap them
std::swap( startIndex, endIndex );
}
for ( int i = startIndex; i <= endIndex; ++i )
{
result.insert( i );
}
}
else
{
result.insert( startIndex );
}
}
}
return result;
}
catch ( const std::exception& e )
{
QString str = QString( "Failed to convert text string \" %1 \" to list of integers : " ).arg( QString::fromStdString( s ) ) +
QString::fromStdString( e.what() );
RiaLogging::error( str );
}
catch ( ... )
{
QString str =
QString( "Failed to convert text string \" %1 \" to list of integers : Caught unknown exception" ).arg( QString::fromStdString( s ) );
RiaLogging::error( str );
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s, int minVal, int maxVal )
{
try
{
std::set<int> result;
std::stringstream stringStream( s );
std::string token;
while ( std::getline( stringStream, token, ',' ) )
{
token = RiaStdStringTools::trimString( token );
// Check for range
size_t dashPos = token.find( '-' );
if ( dashPos != std::string::npos )
{
int startIndex = ( dashPos == 0 ) ? minVal : std::stoi( token.substr( 0, dashPos ) );
int endIndex = ( dashPos == token.size() - 1 ) ? maxVal : std::stoi( token.substr( dashPos + 1 ) );
if ( startIndex > endIndex )
{
// If start is greater than end, swap them
std::swap( startIndex, endIndex );
}
for ( int i = startIndex; i <= endIndex; ++i )
{
result.insert( i );
}
}
else
{
// Check for individual numbers
result.insert( std::stoi( token ) );
}
}
return result;
}
catch ( const std::exception& e )
{
QString str = QString( "Failed to convert text string \" %1 \" to list of integers : " ).arg( QString::fromStdString( s ) ) +
QString::fromStdString( e.what() );
RiaLogging::error( str );
}
catch ( ... )
{
QString str =
QString( "Failed to convert text string \" %1 \" to list of integers : Caught unknown exception" ).arg( QString::fromStdString( s ) );
RiaLogging::error( str );
}
return {};
}

View File

@ -21,6 +21,7 @@
#include <algorithm>
#include <iterator>
#include <numeric>
#include <set>
#include <string>
#include <vector>
@ -60,6 +61,13 @@ public:
static std::string removeHtmlTags( const std::string& s );
// Convert the string "1,2,5-8,10" to {1, 2, 5, 6, 7, 8, 10}
static std::set<int> valuesFromRangeSelection( const std::string& s );
// Convert the range string with support for open ended expressions. minimum and maximum value will be used to limit the ranges.
// The input "-3,5-8,10-", min:1, max:12 will produce {1, 2, 3, 5, 6, 7, 8, 10, 11, 12}
static std::set<int> valuesFromRangeSelection( const std::string& s, int minimumValue, int maximumValue );
private:
template <class Container>
static void splitByDelimiter( const std::string& str, Container& cont, char delimiter = ' ' );

View File

@ -19,6 +19,7 @@
#include "RimEnsembleCurveFilter.h"
#include "RiaCurveDataTools.h"
#include "RiaStdStringTools.h"
#include "RiaSummaryCurveDefinition.h"
#include "RimCustomObjectiveFunction.h"
@ -106,6 +107,8 @@ RimEnsembleCurveFilter::RimEnsembleCurveFilter()
CAF_PDM_InitFieldNoDefault( &m_categories, "Categories", "Categories" );
CAF_PDM_InitFieldNoDefault( &m_realizationFilter, "RealizationFilter", "Realization Filter" );
setDeletable( true );
}
@ -185,6 +188,11 @@ QString RimEnsembleCurveFilter::description() const
QString descriptor;
if ( m_filterMode() == FilterMode::BY_ENSEMBLE_PARAMETER )
{
if ( m_ensembleParameterName() == RiaDefines::summaryRealizationNumber() )
{
return "Realizations : " + m_realizationFilter;
}
descriptor = QString( "%0" ).arg( m_ensembleParameterName() );
}
else if ( m_filterMode() == FilterMode::BY_OBJECTIVE_FUNCTION )
@ -357,7 +365,8 @@ void RimEnsembleCurveFilter::fieldChangedByUi( const caf::PdmFieldHandle* change
}
updateMaxMinAndDefaultValues( true );
}
else if ( changedField == &m_active || changedField == &m_minValue || changedField == &m_maxValue || changedField == &m_categories )
else if ( changedField == &m_active || changedField == &m_minValue || changedField == &m_maxValue || changedField == &m_categories ||
changedField == &m_realizationFilter )
{
if ( curveSet )
{
@ -458,7 +467,11 @@ void RimEnsembleCurveFilter::defineUiOrdering( QString uiConfigName, caf::PdmUiO
uiOrdering.add( &m_customObjectiveFunction );
}
if ( eParam.isNumeric() )
if ( m_ensembleParameterName() == RiaDefines::summaryRealizationNumber() )
{
uiOrdering.add( &m_realizationFilter );
}
else if ( eParam.isNumeric() )
{
uiOrdering.add( &m_minValue );
uiOrdering.add( &m_maxValue );
@ -506,6 +519,19 @@ std::vector<RimSummaryCase*> RimEnsembleCurveFilter::applyFilter( const std::vec
auto ensemble = curveSet ? curveSet->summaryCaseCollection() : nullptr;
if ( !ensemble || !isActive() ) return allSumCases;
bool useIntegerSelection = false;
std::set<int> integerSelection;
if ( m_ensembleParameterName() == RiaDefines::summaryRealizationNumber() )
{
auto eParam = selectedEnsembleParameter();
int minValue = eParam.minValue;
int maxValue = eParam.maxValue;
integerSelection = RiaStdStringTools::valuesFromRangeSelection( m_realizationFilter().toStdString(), minValue, maxValue );
useIntegerSelection = true;
}
std::set<RimSummaryCase*> casesToRemove;
for ( const auto& sumCase : allSumCases )
{
@ -517,7 +543,16 @@ std::vector<RimSummaryCase*> RimEnsembleCurveFilter::applyFilter( const std::vec
auto crpValue = sumCase->caseRealizationParameters()->parameterValue( m_ensembleParameterName() );
if ( eParam.isNumeric() )
if ( useIntegerSelection )
{
int integerValue = crpValue.numericValue();
if ( !integerSelection.contains( integerValue ) )
{
casesToRemove.insert( sumCase );
}
}
else if ( eParam.isNumeric() )
{
if ( !crpValue.isNumeric() || crpValue.numericValue() < m_minValue() || crpValue.numericValue() > m_maxValue() )
{
@ -653,6 +688,19 @@ void RimEnsembleCurveFilter::updateMaxMinAndDefaultValues( bool forceDefault )
m_minValue.uiCapability()->setUiName( QString( "Min (%1)" ).arg( m_lowerLimit ) );
m_maxValue.uiCapability()->setUiName( QString( "Max (%1)" ).arg( m_upperLimit ) );
if ( m_ensembleParameterName() == RiaDefines::summaryRealizationNumber() )
{
int lower = eParam.minValue;
int upper = eParam.maxValue;
m_realizationFilter.uiCapability()->setUiName( QString( "Integer Selection\n[%1..%2]" ).arg( lower ).arg( upper ) );
if ( m_realizationFilter().isEmpty() )
{
m_realizationFilter = QString( "%1-%2" ).arg( lower ).arg( upper );
}
}
}
}
else if ( m_filterMode() == FilterMode::BY_OBJECTIVE_FUNCTION )

View File

@ -105,6 +105,8 @@ private:
caf::PdmField<double> m_maxValue;
caf::PdmField<std::vector<QString>> m_categories;
caf::PdmField<QString> m_realizationFilter;
double m_lowerLimit;
double m_upperLimit;
};

View File

@ -82,7 +82,7 @@ void addCaseRealizationParametersIfFound( RimSummaryCase& sumCase, const QString
int realizationNumber = RifCaseRealizationParametersFileLocator::realizationNumber( modelFolderOrFile );
parameters->setRealizationNumber( realizationNumber );
parameters->addParameter( "RI:REALIZATION_NUM", realizationNumber );
parameters->addParameter( RiaDefines::summaryRealizationNumber(), realizationNumber );
sumCase.setCaseRealizationParameters( parameters );
}

View File

@ -202,3 +202,58 @@ TEST( RiaStdStringToolsTest, DISABLED_PerformanceConversion )
std::cout << "std::from_chars " << std::setw( 9 ) << diff.count() << " s\n";
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaStdStringToolsTest, ValuesFromRangeSelection )
{
std::string testString = "1,2,5-10,15";
std::set<int> expectedValues = { 1, 2, 5, 6, 7, 8, 9, 10, 15 };
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
ASSERT_EQ( expectedValues, actualValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaStdStringToolsTest, ValuesFromRangeSelectionMinMax )
{
std::string testString = "-3, 5, 6-8, 10, 15-";
int minimumValue = 1;
int maximumValue = 20;
std::set<int> expectedValues = { 1, 2, 3, 5, 6, 7, 8, 10, 15, 16, 17, 18, 19, 20 };
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
ASSERT_EQ( expectedValues, actualValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaStdStringToolsTest, TestInvalidRangeStrings )
{
int minimumValue = 1;
int maximumValue = 20;
{
// Handle blank space and inverted from/to
std::string testString = "5, -3, 9-8";
std::set<int> expectedValues = { 1, 2, 3, 5, 8, 9 };
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
ASSERT_EQ( expectedValues, actualValues );
}
{
// If the range is invalid, the result should be an empty set
std::string testString = "5, a";
std::set<int> expectedValues = {};
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
ASSERT_EQ( expectedValues, actualValues );
}
}