#7750 Histogram Plot: Add logarithmic binning and user-defined bin range

Add BinningMode (Linear/Logarithmic) and OutOfRangeHandling (Exclude/Include in
Boundary Bins) to RigHistogramCalculator, with defaulted constructor arguments so
existing callers are unaffected. All four histogram plot data sources get shared
options for binning mode and an optional user-defined bin range through new PDM
fields on RimHistogramDataSource.

The grid statistics data source computes non-default histograms through a new
non-caching RigStatisticsDataCache::computeHistogram() pass-through, leaving the
cached statistics used by the 3D overlay and the P10/P90/mean annotation lines
untouched.

Selecting logarithmic binning enables logarithmic scale on the plot X axis once,
via a new signal handled by RimHistogramCurve.

Also fix the bin index computation to use uniform bins of width range/nBins,
consistent with calculatePercentil() and the bin edges drawn by the plots. The
previous divisor (nBins - 1) shifted counts by up to one bin width relative to
the drawn bars and slightly biased histogram-based percentile estimates.
This commit is contained in:
Kristian Bendiksen
2026-08-13 07:19:18 +02:00
committed by Magne Sjaastad
parent f742fdcac4
commit d96159fb7c
23 changed files with 927 additions and 55 deletions
@@ -78,6 +78,7 @@ void RimEnsembleFractureHistogramDataSource ::defineUiOrdering( QString uiConfig
uiOrdering.add( &m_ensembleFractureStatistics );
uiOrdering.add( &m_property );
uiOrdering.add( &m_numBins );
appendBinningUiOrdering( uiOrdering );
uiOrdering.skipRemainingFields( true );
}
@@ -89,6 +90,8 @@ void RimEnsembleFractureHistogramDataSource::fieldChangedByUi( const caf::PdmFie
const QVariant& oldValue,
const QVariant& newValue )
{
RimHistogramDataSource::fieldChangedByUi( changedField, oldValue, newValue );
dataSourceChanged.send();
}
@@ -119,15 +122,22 @@ RimHistogramDataSource::HistogramResult RimEnsembleFractureHistogramDataSource::
if ( !m_ensembleFractureStatistics() ) return result;
RigHistogramData histogramData =
RigEnsembleFractureStatisticsCalculator::createStatisticsData( m_ensembleFractureStatistics(), m_property(), m_numBins() );
std::optional<std::pair<double, double>> customBinRange;
if ( useUserDefinedBinRange() ) customBinRange = std::make_pair( m_binRangeMin(), m_binRangeMax() );
RigHistogramData histogramData = RigEnsembleFractureStatisticsCalculator::createStatisticsData( m_ensembleFractureStatistics(),
m_property(),
m_numBins(),
binningMode(),
customBinRange,
outOfRangeHandling() );
if ( !histogramData.isHistogramVectorValid() ) return result;
double min = histogramData.min;
double max = histogramData.max;
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative );
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative, binningMode() );
result.valuesY = computeHistogramFrequencies( histogramData.histogram, graphType, frequencyType, cumulative );
result.p10 = histogramData.p10;
@@ -92,6 +92,7 @@ void RimEnsembleParameterHistogramDataSource ::defineUiOrdering( QString uiConfi
uiOrdering.add( &m_ensemble );
uiOrdering.add( &m_parameter );
uiOrdering.add( &m_numBins );
appendBinningUiOrdering( uiOrdering );
uiOrdering.skipRemainingFields( true );
}
@@ -103,6 +104,8 @@ void RimEnsembleParameterHistogramDataSource::fieldChangedByUi( const caf::PdmFi
const QVariant& oldValue,
const QVariant& newValue )
{
RimHistogramDataSource::fieldChangedByUi( changedField, oldValue, newValue );
if ( changedField == &m_ensemble )
{
if ( m_ensemble )
@@ -158,18 +161,22 @@ RimHistogramDataSource::HistogramResult RimEnsembleParameterHistogramDataSource:
auto parameter = m_ensemble->ensembleParameter( m_parameter );
if ( !parameter.isNumeric() || !parameter.isValid() ) return result;
double min = parameter.minValue;
double max = parameter.maxValue;
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative );
std::vector<double> values;
for ( const QVariant& v : parameter.values )
{
values.push_back( v.toDouble() );
}
PosNegAccumulator posNegAccumulator;
posNegAccumulator.addData( values );
auto [min, max] = binRange( parameter.minValue, parameter.maxValue, posNegAccumulator.pos );
if ( min > max || RigStatisticsTools::isInvalidNumber( min ) || RigStatisticsTools::isInvalidNumber( max ) ) return result;
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative, binningMode() );
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( min, max, m_numBins, &histogram );
RigHistogramCalculator histCalc( min, max, m_numBins, &histogram, binningMode(), outOfRangeHandling() );
histCalc.addData( values );
result.valuesY = computeHistogramFrequencies( histogram, graphType, frequencyType, cumulative );
@@ -137,6 +137,7 @@ void RimEnsembleSummaryVectorHistogramDataSource::defineUiOrdering( QString uiCo
uiOrdering.add( &m_summaryAddressUiField );
uiOrdering.add( &m_timeStep );
uiOrdering.add( &m_numBins );
appendBinningUiOrdering( uiOrdering );
uiOrdering.skipRemainingFields( true );
}
@@ -148,6 +149,8 @@ void RimEnsembleSummaryVectorHistogramDataSource::fieldChangedByUi( const caf::P
const QVariant& oldValue,
const QVariant& newValue )
{
RimHistogramDataSource::fieldChangedByUi( changedField, oldValue, newValue );
if ( changedField == &m_ensemble )
{
updateConnectedEditors();
@@ -240,12 +243,16 @@ RimHistogramDataSource::HistogramResult RimEnsembleSummaryVectorHistogramDataSou
auto [min_it, max_it] = std::minmax_element( values.begin(), values.end() );
double min = *min_it;
double max = *max_it;
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative );
PosNegAccumulator posNegAccumulator;
posNegAccumulator.addData( values );
auto [min, max] = binRange( *min_it, *max_it, posNegAccumulator.pos );
if ( min > max || RigStatisticsTools::isInvalidNumber( min ) || RigStatisticsTools::isInvalidNumber( max ) ) return result;
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative, binningMode() );
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( min, max, m_numBins, &histogram );
RigHistogramCalculator histCalc( min, max, m_numBins, &histogram, binningMode(), outOfRangeHandling() );
histCalc.addData( values );
result.valuesY = computeHistogramFrequencies( histogram, graphType, frequencyType, cumulative );
@@ -131,6 +131,7 @@ void RimGridStatisticsHistogramDataSource ::defineUiOrdering( QString uiConfigNa
}
uiOrdering.add( &m_numBins );
appendBinningUiOrdering( uiOrdering );
uiOrdering.skipRemainingFields( true );
}
@@ -142,6 +143,8 @@ void RimGridStatisticsHistogramDataSource::fieldChangedByUi( const caf::PdmField
const QVariant& oldValue,
const QVariant& newValue )
{
RimHistogramDataSource::fieldChangedByUi( changedField, oldValue, newValue );
if ( changedField == &m_case )
{
if ( RimEclipseCase* eclipseCase = dynamic_cast<RimEclipseCase*>( m_case.value() ) )
@@ -195,7 +198,7 @@ RimHistogramDataSource::HistogramResult RimGridStatisticsHistogramDataSource::co
double min = histogramData.min;
double max = histogramData.max;
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative );
result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative, binningMode() );
result.valuesY = computeHistogramFrequencies( histogramData.histogram, graphType, frequencyType, cumulative );
result.p10 = histogramData.p10;
@@ -213,6 +216,10 @@ RigHistogramData RimGridStatisticsHistogramDataSource::createStatisticsData() co
std::unique_ptr<RimHistogramCalculator> histogramCalculator = std::make_unique<RimHistogramCalculator>();
histogramCalculator->setNumBins( static_cast<size_t>( m_numBins() ) );
std::optional<std::pair<double, double>> customBinRange;
if ( useUserDefinedBinRange() ) customBinRange = std::make_pair( m_binRangeMin(), m_binRangeMax() );
histogramCalculator->setBinningParameters( binningMode(), outOfRangeHandling(), customBinRange );
RimHistogramCalculator::StatisticsCellRangeType cellRange = RimHistogramCalculator::StatisticsCellRangeType::ALL_CELLS;
RimHistogramCalculator::StatisticsTimeRangeType timeRange = RimHistogramCalculator::StatisticsTimeRangeType::ALL_TIMESTEPS;
@@ -117,6 +117,7 @@ void RimHistogramCurve::setDataSource( RimHistogramDataSource* dataSource )
m_dataSource->uiCapability()->setUiTreeHidden( true );
m_dataSource->dataSourceChanged.connect( this, &RimHistogramCurve::onDataSourceChanged );
m_dataSource->cumulativeChanged.connect( this, &RimHistogramCurve::onCumulativeChanged );
m_dataSource->logarithmicBinningEnabled.connect( this, &RimHistogramCurve::onLogarithmicBinningEnabled );
}
}
@@ -604,6 +605,19 @@ void RimHistogramCurve::onCumulativeChanged( const caf::SignalEmitter* emitter )
updateCumulativeCurve();
}
//--------------------------------------------------------------------------------------------------
/// Logarithmic bins are best viewed on a logarithmic axis: enable it once when logarithmic binning
/// is selected. The user stays in control of the axis setting afterwards.
//--------------------------------------------------------------------------------------------------
void RimHistogramCurve::onLogarithmicBinningEnabled( const caf::SignalEmitter* emitter )
{
auto plot = firstAncestorOrThisOfType<RimHistogramPlot>();
if ( !plot ) return;
auto axisProperties = dynamic_cast<RimPlotAxisProperties*>( plot->axisPropertiesForPlotAxis( axisX() ) );
if ( axisProperties ) axisProperties->setLogarithmicScaleEnabled( true );
}
//--------------------------------------------------------------------------------------------------
/// Create or delete the cumulative companion curve to match the data source setting. The companion
/// curve shares this curve's data source and is shown on the right axis.
@@ -86,6 +86,7 @@ protected:
void onDataSourceChanged( const caf::SignalEmitter* emitter );
void onCumulativeChanged( const caf::SignalEmitter* emitter );
void onLogarithmicBinningEnabled( const caf::SignalEmitter* emitter );
void updateCumulativeCurve();
void connectReferencedDataSourceSignals();
@@ -18,16 +18,54 @@
#include "RimHistogramDataSource.h"
#include "cafPdmUiOrdering.h"
#include <cmath>
CAF_PDM_XML_ABSTRACT_SOURCE_INIT( RimHistogramDataSource, "HistogramDataSource" );
namespace caf
{
template <>
void caf::AppEnum<RigHistogramCalculator::BinningMode>::setUp()
{
addItem( RigHistogramCalculator::BinningMode::LINEAR, "LINEAR", "Linear" );
addItem( RigHistogramCalculator::BinningMode::LOGARITHMIC, "LOGARITHMIC", "Logarithmic" );
setDefault( RigHistogramCalculator::BinningMode::LINEAR );
}
template <>
void caf::AppEnum<RimHistogramDataSource::BinRangeMode>::setUp()
{
addItem( RimHistogramDataSource::BinRangeMode::AUTOMATIC, "AUTOMATIC", "Automatic" );
addItem( RimHistogramDataSource::BinRangeMode::USER_DEFINED, "USER_DEFINED", "User Defined" );
setDefault( RimHistogramDataSource::BinRangeMode::AUTOMATIC );
}
template <>
void caf::AppEnum<RigHistogramCalculator::OutOfRangeHandling>::setUp()
{
addItem( RigHistogramCalculator::OutOfRangeHandling::EXCLUDE, "EXCLUDE", "Exclude" );
addItem( RigHistogramCalculator::OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS, "INCLUDE_IN_BOUNDARY_BINS", "Include in Boundary Bins" );
setDefault( RigHistogramCalculator::OutOfRangeHandling::EXCLUDE );
}
} // namespace caf
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimHistogramDataSource::RimHistogramDataSource()
: dataSourceChanged( this )
, cumulativeChanged( this )
, logarithmicBinningEnabled( this )
{
CAF_PDM_InitObject( "Histogram Data Source", );
CAF_PDM_InitFieldNoDefault( &m_binningMode, "BinningMode", "Binning Mode" );
CAF_PDM_InitFieldNoDefault( &m_binRangeMode, "BinRangeMode", "Bin Range" );
CAF_PDM_InitField( &m_binRangeMin, "BinRangeMin", 0.0, "Minimum" );
CAF_PDM_InitField( &m_binRangeMax, "BinRangeMax", 1.0, "Maximum" );
CAF_PDM_InitFieldNoDefault( &m_outOfRangeHandling, "OutOfRangeHandling", "Out of Range Values" );
}
//--------------------------------------------------------------------------------------------------
@@ -55,18 +93,115 @@ void RimHistogramDataSource::setShowCumulativeCurve( bool showCumulativeCurve )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double>
RimHistogramDataSource::computeHistogramBins( double min, double max, int numBins, RimHistogramPlot::GraphType graphType, bool cumulative )
void RimHistogramDataSource::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_binningMode && m_binningMode() == RigHistogramCalculator::BinningMode::LOGARITHMIC )
{
logarithmicBinningEnabled.send();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimHistogramDataSource::appendBinningUiOrdering( caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_binningMode );
uiOrdering.add( &m_binRangeMode );
if ( useUserDefinedBinRange() )
{
uiOrdering.add( &m_binRangeMin );
uiOrdering.add( &m_binRangeMax );
uiOrdering.add( &m_outOfRangeHandling );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<double, double> RimHistogramDataSource::computeBinRange( BinRangeMode binRangeMode,
double userMin,
double userMax,
double dataMin,
double dataMax,
RigHistogramCalculator::BinningMode binningMode,
double smallestPositiveValue )
{
double min = dataMin;
double max = dataMax;
if ( binRangeMode == BinRangeMode::USER_DEFINED )
{
min = userMin;
max = userMax;
}
if ( binningMode == RigHistogramCalculator::BinningMode::LOGARITHMIC && min <= 0.0 )
{
min = smallestPositiveValue;
}
return { min, max };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<double, double> RimHistogramDataSource::binRange( double dataMin, double dataMax, double smallestPositiveValue ) const
{
return computeBinRange( m_binRangeMode(), m_binRangeMin(), m_binRangeMax(), dataMin, dataMax, m_binningMode(), smallestPositiveValue );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigHistogramCalculator::BinningMode RimHistogramDataSource::binningMode() const
{
return m_binningMode();
}
//--------------------------------------------------------------------------------------------------
/// Out of range values only exist for a user-defined bin range: an automatic range covers the data
//--------------------------------------------------------------------------------------------------
RigHistogramCalculator::OutOfRangeHandling RimHistogramDataSource::outOfRangeHandling() const
{
if ( !useUserDefinedBinRange() ) return RigHistogramCalculator::OutOfRangeHandling::EXCLUDE;
return m_outOfRangeHandling();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimHistogramDataSource::useUserDefinedBinRange() const
{
return m_binRangeMode() == BinRangeMode::USER_DEFINED;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimHistogramDataSource::computeHistogramBins( double min,
double max,
int numBins,
RimHistogramPlot::GraphType graphType,
bool cumulative,
RigHistogramCalculator::BinningMode binningMode )
{
const bool isLogarithmic = ( binningMode == RigHistogramCalculator::BinningMode::LOGARITHMIC && min > 0.0 );
const double logMin = isLogarithmic ? std::log10( min ) : 0.0;
const double logStep = isLogarithmic ? ( std::log10( max ) - logMin ) / numBins : 0.0;
const double binSize = ( max - min ) / numBins;
auto binEdge = [=]( int i ) { return isLogarithmic ? std::pow( 10.0, logMin + logStep * i ) : min + binSize * i; };
std::vector<double> values;
for ( int i = 0; i < numBins; i++ )
{
if ( graphType == RimHistogramPlot::GraphType::BAR_GRAPH )
{
const double binMin = min + binSize * i;
const double binMax = min + binSize * ( i + 1 );
const double binMin = binEdge( i );
const double binMax = binEdge( i + 1 );
// Close first on left side
if ( i == 0 ) values.push_back( binMin );
@@ -80,7 +215,8 @@ std::vector<double>
}
else if ( graphType == RimHistogramPlot::GraphType::LINE_GRAPH )
{
double centerOfBin = min + binSize * i + binSize / 2.0;
// For logarithmic bins the center is the geometric mean of the bin edges
double centerOfBin = isLogarithmic ? std::pow( 10.0, logMin + logStep * ( i + 0.5 ) ) : min + binSize * i + binSize / 2.0;
values.push_back( centerOfBin );
}
}
@@ -18,11 +18,17 @@
#pragma once
#include "RigStatisticsMath.h"
#include "RimHistogramPlot.h"
#include "cafAppEnum.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include "cafSignal.h"
#include <limits>
#include <utility>
//==================================================================================================
///
@@ -33,6 +39,12 @@ class RimHistogramDataSource : public caf::PdmObject
CAF_PDM_HEADER_INIT;
public:
enum class BinRangeMode
{
AUTOMATIC,
USER_DEFINED
};
struct HistogramResult
{
HistogramResult()
@@ -54,6 +66,7 @@ public:
caf::Signal<> dataSourceChanged;
caf::Signal<> cumulativeChanged;
caf::Signal<> logarithmicBinningEnabled;
virtual std::string unitNameX() const = 0;
virtual std::string unitNameY() const = 0;
@@ -69,7 +82,12 @@ public:
virtual std::string name() const = 0;
static std::vector<double>
computeHistogramBins( double min, double max, int numBins, RimHistogramPlot::GraphType graphType, bool cumulative = false );
computeHistogramBins( double min,
double max,
int numBins,
RimHistogramPlot::GraphType graphType,
bool cumulative = false,
RigHistogramCalculator::BinningMode binningMode = RigHistogramCalculator::BinningMode::LINEAR );
static std::vector<double> computeHistogramFrequencies( const std::vector<size_t>& values,
RimHistogramPlot::GraphType graphType,
RimHistogramPlot::FrequencyType frequencyType,
@@ -78,4 +96,31 @@ public:
RimHistogramPlot::GraphType graphType,
RimHistogramPlot::FrequencyType frequencyType,
bool cumulative = false );
// Resolve the effective bin range: the user-defined range when USER_DEFINED, otherwise [dataMin, dataMax].
// For logarithmic binning a non-positive minimum is replaced by smallestPositiveValue.
static std::pair<double, double> computeBinRange( BinRangeMode binRangeMode,
double userMin,
double userMax,
double dataMin,
double dataMax,
RigHistogramCalculator::BinningMode binningMode,
double smallestPositiveValue );
protected:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void appendBinningUiOrdering( caf::PdmUiOrdering& uiOrdering );
std::pair<double, double> binRange( double dataMin, double dataMax, double smallestPositiveValue ) const;
RigHistogramCalculator::BinningMode binningMode() const;
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling() const;
bool useUserDefinedBinRange() const;
caf::PdmField<caf::AppEnum<RigHistogramCalculator::BinningMode>> m_binningMode;
caf::PdmField<caf::AppEnum<BinRangeMode>> m_binRangeMode;
caf::PdmField<double> m_binRangeMin;
caf::PdmField<double> m_binRangeMax;
caf::PdmField<caf::AppEnum<RigHistogramCalculator::OutOfRangeHandling>> m_outOfRangeHandling;
};
@@ -86,6 +86,61 @@ void RimHistogramCalculator::setNumBins( size_t numBins )
m_numBins = numBins;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimHistogramCalculator::setBinningParameters( RigHistogramCalculator::BinningMode binningMode,
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling,
std::optional<std::pair<double, double>> customBinRange )
{
m_binningMode = binningMode;
m_outOfRangeHandling = outOfRangeHandling;
m_customBinRange = customBinRange;
}
//--------------------------------------------------------------------------------------------------
/// Recompute the histogram with non-default binning (logarithmic and/or a custom bin range) directly
/// from the underlying data. The statistics cached for other consumers are left untouched.
//--------------------------------------------------------------------------------------------------
void RimHistogramCalculator::applyCustomBinning( RigStatisticsDataCache* statisticsCache,
std::optional<size_t> timeStepIndex,
RigHistogramData& histData )
{
const bool useCustomBinning = ( m_binningMode != RigHistogramCalculator::BinningMode::LINEAR ) || m_customBinRange.has_value();
if ( !useCustomBinning || !statisticsCache ) return;
double binMin = m_customBinRange ? m_customBinRange->first : histData.min;
double binMax = m_customBinRange ? m_customBinRange->second : histData.max;
if ( m_binningMode == RigHistogramCalculator::BinningMode::LOGARITHMIC && binMin <= 0.0 )
{
// Use the smallest positive value as the logarithmic range minimum. HUGE_VAL when there are none.
double pos = HUGE_VAL;
double neg = -HUGE_VAL;
if ( timeStepIndex.has_value() )
statisticsCache->posNegClosestToZero( *timeStepIndex, pos, neg );
else
statisticsCache->posNegClosestToZero( pos, neg );
binMin = pos;
}
histData.histogram.clear();
if ( binMin <= binMax && RigStatisticsTools::isValidNumber( binMin ) && RigStatisticsTools::isValidNumber( binMax ) )
{
RigHistogramCalculator histCalc( binMin, binMax, m_numBins, &histData.histogram, m_binningMode, m_outOfRangeHandling );
if ( timeStepIndex.has_value() )
statisticsCache->computeHistogram( *timeStepIndex, histCalc );
else
statisticsCache->computeHistogram( histCalc );
// Let the plot draw bin edges that match the histogram
histData.min = binMin;
histData.max = binMax;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -198,6 +253,9 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
RigHistogramData histData;
RigStatisticsDataCache* statisticsCache = nullptr;
std::optional<size_t> timeStepContext;
eclResultDefinition->loadResult();
if ( eclResultDefinition->isFlowDiagOrInjectionFlooding() )
@@ -217,6 +275,9 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
fldResults->mobileVolumeWeightedMean( resAddr, timeStep, &histData.weightedMean );
histData.histogram = fldResults->scalarValuesHistogram( resAddr, timeStep );
statisticsCache = fldResults->statistics( resAddr );
timeStepContext = static_cast<size_t>( timeStep );
}
else if ( cellRange == StatisticsCellRangeType::VISIBLE_CELLS )
{
@@ -232,6 +293,9 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
m_visibleCellStatistics->mobileVolumeWeightedMean( timeStep, histData.weightedMean );
histData.histogram = m_visibleCellStatistics->cellScalarValuesHistogram( timeStep );
statisticsCache = m_visibleCellStatistics.p();
timeStepContext = static_cast<size_t>( timeStep );
}
}
else if ( timeRange == StatisticsTimeRangeType::ALL_TIMESTEPS )
@@ -249,6 +313,8 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
fldResults->mobileVolumeWeightedMean( resAddr, &histData.weightedMean );
histData.histogram = fldResults->scalarValuesHistogram( resAddr );
statisticsCache = fldResults->statistics( resAddr );
}
else if ( cellRange == StatisticsCellRangeType::VISIBLE_CELLS )
{
@@ -264,6 +330,8 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
m_visibleCellStatistics->mobileVolumeWeightedMean( histData.weightedMean );
histData.histogram = m_visibleCellStatistics->cellScalarValuesHistogram();
statisticsCache = m_visibleCellStatistics.p();
}
}
}
@@ -282,6 +350,8 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
cellResults->sumCellScalarValues( eclResAddr, histData.sum );
cellResults->mobileVolumeWeightedMean( eclResAddr, histData.weightedMean );
histData.histogram = cellResults->cellScalarValuesHistogram( eclResAddr );
statisticsCache = cellResults->statistics( eclResAddr );
}
else if ( timeRange == StatisticsTimeRangeType::CURRENT_TIMESTEP )
{
@@ -292,6 +362,9 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
cellResults->sumCellScalarValues( eclResAddr, timeStep, histData.sum );
cellResults->mobileVolumeWeightedMean( eclResAddr, timeStep, histData.weightedMean );
histData.histogram = cellResults->cellScalarValuesHistogram( eclResAddr, timeStep );
statisticsCache = cellResults->statistics( eclResAddr );
timeStepContext = static_cast<size_t>( timeStep );
}
}
}
@@ -310,6 +383,8 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
m_visibleCellStatistics->mobileVolumeWeightedMean( histData.weightedMean );
histData.histogram = m_visibleCellStatistics->cellScalarValuesHistogram();
statisticsCache = m_visibleCellStatistics.p();
}
else if ( timeRange == StatisticsTimeRangeType::CURRENT_TIMESTEP )
{
@@ -321,8 +396,14 @@ RigHistogramData RimHistogramCalculator::histogramData( RimEclipseView*
m_visibleCellStatistics->mobileVolumeWeightedMean( timeStep, histData.weightedMean );
histData.histogram = m_visibleCellStatistics->cellScalarValuesHistogram( timeStep );
statisticsCache = m_visibleCellStatistics.p();
timeStepContext = static_cast<size_t>( timeStep );
}
}
applyCustomBinning( statisticsCache, timeStepContext, histData );
return histData;
}
@@ -23,9 +23,13 @@
#include "RigHistogramData.h"
#include "RigStatisticsDataCache.h"
#include "RigStatisticsMath.h"
#include "cvfObject.h"
#include <optional>
#include <utility>
class RimEclipseResultDefinition;
class RimGeoMechContourMapView;
class RimEclipseContourMapView;
@@ -55,6 +59,10 @@ public:
void setNumBins( size_t numBins );
void setBinningParameters( RigHistogramCalculator::BinningMode binningMode,
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling,
std::optional<std::pair<double, double>> customBinRange );
RigHistogramData histogramData( RimEclipseContourMapView* contourMap );
RigHistogramData histogramData( RimGeoMechContourMapView* contourMap );
RigHistogramData histogramData( RimEclipseView* eclipseView, StatisticsCellRangeType cellRange, StatisticsTimeRangeType timeRange );
@@ -74,9 +82,15 @@ private:
void updateVisCellStatsIfNeeded( RimGeoMechView* geoMechView );
void applyCustomBinning( RigStatisticsDataCache* statisticsCache, std::optional<size_t> timeStepIndex, RigHistogramData& histData );
std::vector<RigEclipseResultAddress> sourcesForMultiPropertyResults( const QString& resultName );
bool m_isVisCellStatUpToDate;
cvf::ref<RigStatisticsDataCache> m_visibleCellStatistics;
size_t m_numBins;
RigHistogramCalculator::BinningMode m_binningMode = RigHistogramCalculator::BinningMode::LINEAR;
RigHistogramCalculator::OutOfRangeHandling m_outOfRangeHandling = RigHistogramCalculator::OutOfRangeHandling::EXCLUDE;
std::optional<std::pair<double, double>> m_customBinRange;
};
@@ -795,3 +795,15 @@ bool RimPlotAxisProperties::isLogarithmicScaleEnabled() const
{
return m_isLogarithmicScaleEnabled;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimPlotAxisProperties::setLogarithmicScaleEnabled( bool enabled )
{
if ( m_isLogarithmicScaleEnabled() == enabled ) return;
m_isLogarithmicScaleEnabled = enabled;
updateConnectedEditors();
logarithmicChanged.send( enabled );
}
@@ -118,6 +118,7 @@ public:
void removeAllAnnotations() override;
bool isLogarithmicScaleEnabled() const override;
void setLogarithmicScaleEnabled( bool enabled );
bool isActive() const override;
void showAnnotationObjectsInProjectTree();
@@ -158,6 +158,8 @@ public:
void setStatisticsDataCacheNumBins( const RigEclipseResultAddress& resultAddress, size_t numBins );
RigStatisticsDataCache* statistics( const RigEclipseResultAddress& resVarAddr );
size_t addStaticScalarResult( RiaDefines::ResultCatType type, const QString& resultName, bool needsToBeStored, size_t resultValueCount );
RigEclipseResultAddress defaultResult() const;
@@ -247,8 +249,6 @@ private:
void assignValuesToTemporaryLgrs( const QString& resultName, std::vector<double>& values );
RigStatisticsDataCache* statistics( const RigEclipseResultAddress& resVarAddr );
bool isRadialModel() const;
private:
@@ -57,9 +57,12 @@ void caf::AppEnum<RigEnsembleFractureStatisticsCalculator::PropertyType>::setUp(
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData( const RimEnsembleFractureStatistics* esf,
PropertyType propertyType,
int numBins )
RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData( const RimEnsembleFractureStatistics* esf,
PropertyType propertyType,
int numBins,
RigHistogramCalculator::BinningMode binningMode,
std::optional<std::pair<double, double>> customBinRange,
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling )
{
std::vector<cvf::ref<RigStimPlanFractureDefinition>> fractureDefinitions = esf->readFractureDefinitions();
@@ -68,7 +71,7 @@ RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData(
fractureDefinitions = RigEnsembleFractureStatisticsCalculator::removeZeroWidthDefinitions( fractureDefinitions );
}
return createStatisticsData( fractureDefinitions, propertyType, numBins );
return createStatisticsData( fractureDefinitions, propertyType, numBins, binningMode, customBinRange, outOfRangeHandling );
}
//--------------------------------------------------------------------------------------------------
@@ -76,8 +79,11 @@ RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData(
//--------------------------------------------------------------------------------------------------
RigHistogramData
RigEnsembleFractureStatisticsCalculator::createStatisticsData( const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
PropertyType propertyType,
int numBins )
PropertyType propertyType,
int numBins,
RigHistogramCalculator::BinningMode binningMode,
std::optional<std::pair<double, double>> customBinRange,
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling )
{
std::vector<double> samples = calculateProperty( fractureDefinitions, propertyType );
@@ -97,12 +103,30 @@ RigHistogramData
&mean,
RigStatisticsMath::PercentileStyle::SWITCHED );
std::vector<size_t> histogram;
RigHistogramCalculator histogramCalculator( histogramData.min, histogramData.max, numBins, &histogram );
for ( auto s : samples )
histogramCalculator.addValue( s );
double binMin = customBinRange ? customBinRange->first : histogramData.min;
double binMax = customBinRange ? customBinRange->second : histogramData.max;
histogramData.histogram = histogram;
if ( binningMode == RigHistogramCalculator::BinningMode::LOGARITHMIC && binMin <= 0.0 )
{
// Use the smallest positive value as the logarithmic range minimum. HUGE_VAL when there are none.
PosNegAccumulator posNegAccumulator;
posNegAccumulator.addData( samples );
binMin = posNegAccumulator.pos;
}
if ( !samples.empty() && binMin <= binMax && RigStatisticsTools::isValidNumber( binMin ) && RigStatisticsTools::isValidNumber( binMax ) )
{
std::vector<size_t> histogram;
RigHistogramCalculator histogramCalculator( binMin, binMax, numBins, &histogram, binningMode, outOfRangeHandling );
for ( auto s : samples )
histogramCalculator.addValue( s );
histogramData.histogram = histogram;
// Let the plot draw bin edges that match the histogram
histogramData.min = binMin;
histogramData.max = binMax;
}
return histogramData;
}
@@ -21,12 +21,15 @@
#include "RiaDefines.h"
#include "RigHistogramData.h"
#include "RigStatisticsMath.h"
#include "cafPdmUiNumberFormat.h"
#include "cvfObject.h"
#include <functional>
#include <optional>
#include <utility>
#include <vector>
class RimEnsembleFractureStatistics;
@@ -51,11 +54,21 @@ public:
FORMATION_DIP
};
static RigHistogramData createStatisticsData( const RimEnsembleFractureStatistics* esf, PropertyType propertyType, int numBins );
static RigHistogramData createStatisticsData(
const RimEnsembleFractureStatistics* esf,
PropertyType propertyType,
int numBins,
RigHistogramCalculator::BinningMode binningMode = RigHistogramCalculator::BinningMode::LINEAR,
std::optional<std::pair<double, double>> customBinRange = std::nullopt,
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling = RigHistogramCalculator::OutOfRangeHandling::EXCLUDE );
static RigHistogramData createStatisticsData( const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
PropertyType propertyType,
int numBins );
static RigHistogramData createStatisticsData(
const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
PropertyType propertyType,
int numBins,
RigHistogramCalculator::BinningMode binningMode = RigHistogramCalculator::BinningMode::LINEAR,
std::optional<std::pair<double, double>> customBinRange = std::nullopt,
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling = RigHistogramCalculator::OutOfRangeHandling::EXCLUDE );
static std::vector<cvf::ref<RigStimPlanFractureDefinition>>
removeZeroWidthDefinitions( const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions );
@@ -97,6 +97,8 @@ public:
void setStatisticsDataCacheNumBins( const RigFlowDiagResultAddress& resVarAddr, size_t numBins );
RigStatisticsDataCache* statistics( const RigFlowDiagResultAddress& resVarAddr );
private:
const std::vector<double>* findOrCalculateResult( const RigFlowDiagResultAddress& resVarAddr, size_t timeStepIndex );
void calculateNativeResultsIfNotPreviouslyAttempted( size_t timeStepIndex, RigFlowDiagResultAddress::PhaseSelection phaseSelection );
@@ -129,8 +131,6 @@ private:
size_t activeCellCount,
std::vector<double>* sumOfFractions );
RigStatisticsDataCache* statistics( const RigFlowDiagResultAddress& resVarAddr );
RigFlowDiagResultFrames* createScalarResult( const RigFlowDiagResultAddress& resVarAddr );
RigFlowDiagResultFrames* findScalarResult( const RigFlowDiagResultAddress& resVarAddr );
std::vector<double>* findScalarResultFrame( const RigFlowDiagResultAddress& resVarAddr, size_t timeStepIndex );
@@ -283,6 +283,22 @@ const std::vector<size_t>& RigStatisticsDataCache::cellScalarValuesHistogram( si
return m_statsPrTs[timeStepIndex].m_histogram;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigStatisticsDataCache::computeHistogram( RigHistogramCalculator& histogramCalculator )
{
m_statisticsCalculator->addDataToHistogramCalculator( histogramCalculator );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigStatisticsDataCache::computeHistogram( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator )
{
m_statisticsCalculator->addDataToHistogramCalculator( timeStepIndex, histogramCalculator );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -57,6 +57,10 @@ public:
const std::vector<size_t>& cellScalarValuesHistogram();
const std::vector<size_t>& cellScalarValuesHistogram( size_t timeStepIndex );
// Fills the supplied histogram calculator from the underlying data. Nothing is cached.
void computeHistogram( RigHistogramCalculator& histogramCalculator );
void computeHistogram( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator );
const std::vector<int>& uniqueCellScalarValues();
const std::vector<int>& uniqueCellScalarValues( size_t timeStepIndex );
@@ -451,7 +451,12 @@ double RigStatisticsMath::calculateMean( const std::vector<double>& values )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigHistogramCalculator::RigHistogramCalculator( double min, double max, size_t nBins, std::vector<size_t>* histogram )
RigHistogramCalculator::RigHistogramCalculator( double min,
double max,
size_t nBins,
std::vector<size_t>* histogram,
BinningMode binningMode,
OutOfRangeHandling outOfRangeHandling )
{
assert( histogram );
assert( nBins > 0 );
@@ -461,16 +466,33 @@ RigHistogramCalculator::RigHistogramCalculator( double min, double max, size_t n
nBins = 1;
} // Avoid dividing on 0 range
m_histogram = histogram;
m_min = min;
m_observationCount = 0;
// Logarithmic binning requires a positive range
if ( binningMode == BinningMode::LOGARITHMIC && min <= 0.0 )
{
binningMode = BinningMode::LINEAR;
}
m_histogram = histogram;
m_binningMode = binningMode;
m_outOfRangeHandling = outOfRangeHandling;
m_observationCount = 0;
// Initialize bins
m_histogram->resize( nBins );
for ( size_t i = 0; i < m_histogram->size(); ++i )
( *m_histogram )[i] = 0;
m_range = max - min;
if ( m_binningMode == BinningMode::LOGARITHMIC )
{
m_min = std::log10( min );
m_max = std::log10( max );
}
else
{
m_min = min;
m_max = max;
}
m_range = m_max - m_min;
m_maxIndex = nBins - 1;
}
@@ -481,15 +503,37 @@ void RigHistogramCalculator::addValue( double value )
{
if ( RigStatisticsTools::isInvalidNumber<double>( value ) ) return;
double transformed = value;
if ( m_binningMode == BinningMode::LOGARITHMIC )
{
if ( value <= 0.0 )
{
// Non-positive values cannot be represented on a logarithmic scale and are below any valid range
if ( m_outOfRangeHandling != OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS ) return;
( *m_histogram )[0]++;
m_observationCount++;
return;
}
transformed = std::log10( value );
}
if ( transformed < m_min || transformed > m_max )
{
if ( m_outOfRangeHandling != OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS ) return;
size_t boundaryIndex = transformed < m_min ? 0 : m_maxIndex;
( *m_histogram )[boundaryIndex]++;
m_observationCount++;
return;
}
size_t index = 0;
if ( m_maxIndex > 0 ) index = (size_t)( m_maxIndex * ( value - m_min ) / m_range );
// Uniform bins of width m_range / nBins, consistent with calculatePercentil() and the bin edges
// drawn by the histogram plots. The maximum value is included in the last bin.
if ( m_maxIndex > 0 ) index = std::min( (size_t)( m_histogram->size() * ( transformed - m_min ) / m_range ), m_maxIndex );
if ( index < m_histogram->size() ) // Just clip to the max min range (-index will overflow to positive )
{
( *m_histogram )[index]++;
m_observationCount++;
}
( *m_histogram )[index]++;
m_observationCount++;
}
//--------------------------------------------------------------------------------------------------
@@ -545,8 +589,11 @@ double RigHistogramCalculator::calculatePercentil( double pVal, RigStatisticsMat
pValClamped = 1.0 - pValClamped;
}
auto toDomainValue = [this]( double binSpaceValue )
{ return m_binningMode == BinningMode::LOGARITHMIC ? std::pow( 10.0, binSpaceValue ) : binSpaceValue; };
double pValObservationCount = pValClamped * m_observationCount;
if ( pValObservationCount == 0.0 ) return m_min;
if ( pValObservationCount == 0.0 ) return toDomainValue( m_min );
size_t accObsCount = 0;
double binWidth = m_range / m_histogram->size();
@@ -564,7 +611,8 @@ double RigHistogramCalculator::calculatePercentil( double pVal, RigStatisticsMat
// See https://resinsight.org/docs/casegroupsandstatistics/#percentile-methods for details
return histogramBasedEstimate;
// For logarithmic binning the interpolation happens in log10 space
return toDomainValue( histogramBasedEstimate );
}
}
assert( false );
@@ -65,7 +65,24 @@ public:
class RigHistogramCalculator
{
public:
RigHistogramCalculator( double min, double max, size_t nBins, std::vector<size_t>* histogram );
enum class BinningMode
{
LINEAR,
LOGARITHMIC
};
enum class OutOfRangeHandling
{
EXCLUDE,
INCLUDE_IN_BOUNDARY_BINS
};
RigHistogramCalculator( double min,
double max,
size_t nBins,
std::vector<size_t>* histogram,
BinningMode binningMode = BinningMode::LINEAR,
OutOfRangeHandling outOfRangeHandling = OutOfRangeHandling::EXCLUDE );
void addData( const std::vector<double>& data );
void addData( const std::vector<float>& data );
@@ -74,7 +91,7 @@ public:
/// Calculates the estimated percentile from the histogram.
/// the percentile is the domain value at which pVal of the observations are below it.
/// Will only consider observed values between min and max, as all other values are discarded from the histogram
/// The estimate is only meaningful when the histogram covers the full range of the observed values.
double calculatePercentil( double pVal, RigStatisticsMath::PercentileStyle percentileStyle );
@@ -82,6 +99,9 @@ private:
size_t m_maxIndex;
double m_range;
double m_min;
double m_max;
BinningMode m_binningMode;
OutOfRangeHandling m_outOfRangeHandling;
size_t m_observationCount;
std::vector<size_t>* m_histogram;
};
@@ -23,3 +23,31 @@ TEST( RigEnsembleFractureStatisticsCalculatorTest, NoConductivityResultNames )
EXPECT_TRUE( values.empty() );
}
}
//--------------------------------------------------------------------------------------------------
/// The default binning arguments must produce an invalid histogram for empty input, for both the
/// default and the custom binning code paths
//--------------------------------------------------------------------------------------------------
TEST( RigEnsembleFractureStatisticsCalculatorTest, EmptyDefinitionsProduceInvalidHistogram )
{
std::vector<cvf::ref<RigStimPlanFractureDefinition>> definitions;
{
RigHistogramData histogramData =
RigEnsembleFractureStatisticsCalculator::createStatisticsData( definitions,
RigEnsembleFractureStatisticsCalculator::PropertyType::HEIGHT,
50 );
EXPECT_FALSE( histogramData.isHistogramVectorValid() );
}
{
RigHistogramData histogramData =
RigEnsembleFractureStatisticsCalculator::createStatisticsData( definitions,
RigEnsembleFractureStatisticsCalculator::PropertyType::HEIGHT,
50,
RigHistogramCalculator::BinningMode::LOGARITHMIC,
std::make_pair( 1.0, 100.0 ),
RigHistogramCalculator::OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS );
EXPECT_FALSE( histogramData.isHistogramVectorValid() );
}
}
@@ -20,10 +20,12 @@
#include "gtest/gtest.h"
#include "RigStatisticsDataCache.h"
#include "RigStatisticsMath.h"
#include "QElapsedTimer"
#include <cmath>
#include <numeric>
//--------------------------------------------------------------------------------------------------
@@ -136,8 +138,8 @@ TEST( RigStatisticsMath, HistogramPercentiles )
p90 = histCalc.calculatePercentil( 0.9, RigStatisticsMath::PercentileStyle::REGULAR );
EXPECT_DOUBLE_EQ( -76273.240559989776, p10 );
EXPECT_DOUBLE_EQ( 5312.1312871307755, p50 );
EXPECT_DOUBLE_EQ( 94818.413022321271, p90 );
EXPECT_DOUBLE_EQ( 7292.3587591482656, p50 );
EXPECT_DOUBLE_EQ( 96798.640494338761, p90 );
}
//--------------------------------------------------------------------------------------------------
@@ -348,6 +350,263 @@ TEST( RigStatisticsMath, calculateMean )
}
}
//--------------------------------------------------------------------------------------------------
/// Default constructor arguments: linear binning, out-of-range values excluded
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, LinearHistogramBinning )
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 0.0, 10.0, 5, &histogram );
histCalc.addData( std::vector<double>{ -0.1, 0.0, 3.0, 4.9, 7.0, 9.9, 10.0, 10.1, HUGE_VAL, -HUGE_VAL, std::nan( "" ) } );
// Uniform bins of width 2. Values outside [0, 10] and invalid numbers are discarded.
// The maximum value is included in the last bin.
std::vector<size_t> expected = { 1, 1, 1, 1, 2 };
EXPECT_EQ( expected, histogram );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, LogarithmicHistogramBinning )
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 1.0, 1000.0, 3, &histogram, RigHistogramCalculator::BinningMode::LOGARITHMIC );
histCalc.addData( std::vector<double>{ 0.5, 1.0, 2.0, 20.0, 200.0, 999.0, 1000.0, 2000.0 } );
// One bin per decade: [1, 10), [10, 100), [100, 1000]. Values outside the range are discarded.
std::vector<size_t> expected = { 2, 1, 3 };
EXPECT_EQ( expected, histogram );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, CustomRangeExcludesOutOfRangeValues )
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 10.0, 20.0, 2, &histogram );
histCalc.addData( std::vector<double>{ 5.0, 12.0, 18.0, 20.0, 25.0 } );
std::vector<size_t> expected = { 1, 2 };
EXPECT_EQ( expected, histogram );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, CustomRangeClampsToBoundaryBins )
{
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 10.0,
20.0,
2,
&histogram,
RigHistogramCalculator::BinningMode::LINEAR,
RigHistogramCalculator::OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS );
histCalc.addData( std::vector<double>{ 5.0, 12.0, 18.0, 20.0, 25.0, HUGE_VAL, -HUGE_VAL, std::nan( "" ) } );
// Below range -> first bin, above range -> last bin, invalid numbers still skipped
std::vector<size_t> expected = { 2, 3 };
EXPECT_EQ( expected, histogram );
}
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 10.0,
1000.0,
2,
&histogram,
RigHistogramCalculator::BinningMode::LOGARITHMIC,
RigHistogramCalculator::OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS );
histCalc.addData( std::vector<double>{ 5.0, 50.0, 2000.0 } );
std::vector<size_t> expected = { 2, 1 };
EXPECT_EQ( expected, histogram );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, LogarithmicBinningNonPositiveValues )
{
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 1.0, 100.0, 2, &histogram, RigHistogramCalculator::BinningMode::LOGARITHMIC );
histCalc.addData( std::vector<double>{ 0.0, -5.0, 5.0 } );
std::vector<size_t> expected = { 1, 0 };
EXPECT_EQ( expected, histogram );
}
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 1.0,
100.0,
2,
&histogram,
RigHistogramCalculator::BinningMode::LOGARITHMIC,
RigHistogramCalculator::OutOfRangeHandling::INCLUDE_IN_BOUNDARY_BINS );
histCalc.addData( std::vector<double>{ 0.0, -5.0, 5.0 } );
// Non-positive values cannot be represented on a logarithmic scale and count in the first bin
std::vector<size_t> expected = { 3, 0 };
EXPECT_EQ( expected, histogram );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, LogarithmicHistogramPercentiles )
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 1.0, 10000.0, 4, &histogram, RigHistogramCalculator::BinningMode::LOGARITHMIC );
histCalc.addData( std::vector<double>{ 1.0, 10.0, 100.0, 1000.0, 10000.0 } );
// Percentile interpolation happens in log10 space, results are transformed back to the value domain
double p10 = histCalc.calculatePercentil( 0.1, RigStatisticsMath::PercentileStyle::REGULAR );
double p50 = histCalc.calculatePercentil( 0.5, RigStatisticsMath::PercentileStyle::REGULAR );
EXPECT_DOUBLE_EQ( std::pow( 10.0, 0.5 ), p10 );
EXPECT_DOUBLE_EQ( std::pow( 10.0, 2.5 ), p50 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, HistogramDegenerateRanges )
{
{
// Zero range collapses to a single bin
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 5.0, 5.0, 3, &histogram );
histCalc.addValue( 5.0 );
std::vector<size_t> expected = { 1 };
EXPECT_EQ( expected, histogram );
}
{
// Logarithmic binning with a non-positive minimum falls back to linear binning
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 0.0, 10.0, 2, &histogram, RigHistogramCalculator::BinningMode::LOGARITHMIC );
histCalc.addValue( 7.0 );
std::vector<size_t> expected = { 0, 1 };
EXPECT_EQ( expected, histogram );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
namespace
{
class VectorBackedStatisticsCalculator : public RigStatisticsCalculator
{
public:
explicit VectorBackedStatisticsCalculator( const std::vector<std::vector<double>>& valuesPerTimeStep )
: m_valuesPerTimeStep( valuesPerTimeStep )
{
}
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override
{
MinMaxAccumulator accumulator( min, max );
accumulator.addData( m_valuesPerTimeStep[timeStepIndex] );
min = accumulator.min;
max = accumulator.max;
}
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override
{
PosNegAccumulator accumulator( pos, neg );
accumulator.addData( m_valuesPerTimeStep[timeStepIndex] );
pos = accumulator.pos;
neg = accumulator.neg;
}
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override
{
SumCountAccumulator accumulator( valueSum, sampleCount );
accumulator.addData( m_valuesPerTimeStep[timeStepIndex] );
valueSum = accumulator.valueSum;
sampleCount = accumulator.sampleCount;
}
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override
{
histogramCalculator.addData( m_valuesPerTimeStep[timeStepIndex] );
}
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override {}
size_t timeStepCount() override { return m_valuesPerTimeStep.size(); }
private:
std::vector<std::vector<double>> m_valuesPerTimeStep;
};
} // namespace
//--------------------------------------------------------------------------------------------------
/// computeHistogram() fills a custom-configured histogram calculator without disturbing the
/// statistics cached for other consumers
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsMath, StatisticsDataCacheComputeHistogramPassThrough )
{
cvf::ref<VectorBackedStatisticsCalculator> calculator = new VectorBackedStatisticsCalculator( { { 1.0, 10.0, 100.0 }, { 1000.0, 0.5 } } );
cvf::ref<RigStatisticsDataCache> cache = new RigStatisticsDataCache( calculator.p() );
std::vector<size_t> cachedHistogramBefore = cache->cellScalarValuesHistogram();
double minBefore, maxBefore, p10Before, p90Before;
cache->minMaxCellScalarValues( minBefore, maxBefore );
cache->p10p90CellScalarValues( p10Before, p90Before );
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 1.0, 1000.0, 3, &histogram, RigHistogramCalculator::BinningMode::LOGARITHMIC );
cache->computeHistogram( histCalc );
// All time steps: {1, 10, 100, 1000, 0.5} in decade bins, 0.5 is below range and discarded
std::vector<size_t> expected = { 1, 1, 2 };
EXPECT_EQ( expected, histogram );
}
{
std::vector<size_t> histogram;
RigHistogramCalculator histCalc( 1.0, 1000.0, 3, &histogram, RigHistogramCalculator::BinningMode::LOGARITHMIC );
cache->computeHistogram( 0, histCalc );
std::vector<size_t> expected = { 1, 1, 1 };
EXPECT_EQ( expected, histogram );
}
// The cached statistics are not perturbed by the custom histogram computations
EXPECT_EQ( cachedHistogramBefore, cache->cellScalarValuesHistogram() );
double minAfter, maxAfter, p10After, p90After;
cache->minMaxCellScalarValues( minAfter, maxAfter );
cache->p10p90CellScalarValues( p10After, p90After );
EXPECT_DOUBLE_EQ( minBefore, minAfter );
EXPECT_DOUBLE_EQ( maxBefore, maxAfter );
EXPECT_DOUBLE_EQ( p10Before, p10After );
EXPECT_DOUBLE_EQ( p90Before, p90After );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -99,3 +99,128 @@ TEST( RimHistogramDataSourceTest, NonCumulativeIsUnchanged )
EXPECT_NEAR( 2.0, frequencies[1], 1e-9 );
EXPECT_NEAR( 3.0, frequencies[2], 1e-9 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimHistogramDataSourceTest, LinearBinsUnchangedWithDefaultBinningMode )
{
std::vector<double> xValues = RimHistogramDataSource::computeHistogramBins( 0.0, 10.0, 2, RimHistogramPlot::GraphType::BAR_GRAPH, false );
std::vector<double> expected = { 0.0, 0.0, 5.0, 5.0, 10.0, 10.0 };
ASSERT_EQ( expected.size(), xValues.size() );
for ( size_t i = 0; i < expected.size(); i++ )
{
EXPECT_NEAR( expected[i], xValues[i], 1e-9 );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimHistogramDataSourceTest, LogarithmicBinsBarGraph )
{
std::vector<double> xValues = RimHistogramDataSource::computeHistogramBins( 1.0,
1000.0,
3,
RimHistogramPlot::GraphType::BAR_GRAPH,
false,
RigHistogramCalculator::BinningMode::LOGARITHMIC );
std::vector<double> expected = { 1.0, 1.0, 10.0, 10.0, 100.0, 100.0, 1000.0, 1000.0 };
ASSERT_EQ( expected.size(), xValues.size() );
for ( size_t i = 0; i < expected.size(); i++ )
{
EXPECT_NEAR( expected[i], xValues[i], 1e-9 );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimHistogramDataSourceTest, LogarithmicBinsCumulativeBarGraph )
{
std::vector<double> xValues = RimHistogramDataSource::computeHistogramBins( 1.0,
1000.0,
3,
RimHistogramPlot::GraphType::BAR_GRAPH,
true,
RigHistogramCalculator::BinningMode::LOGARITHMIC );
// A cumulative curve is not closed on the right side
std::vector<double> expected = { 1.0, 1.0, 10.0, 10.0, 100.0, 100.0, 1000.0 };
ASSERT_EQ( expected.size(), xValues.size() );
for ( size_t i = 0; i < expected.size(); i++ )
{
EXPECT_NEAR( expected[i], xValues[i], 1e-9 );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimHistogramDataSourceTest, LogarithmicBinsLineGraph )
{
std::vector<double> xValues = RimHistogramDataSource::computeHistogramBins( 1.0,
1000.0,
3,
RimHistogramPlot::GraphType::LINE_GRAPH,
false,
RigHistogramCalculator::BinningMode::LOGARITHMIC );
// Bin centers are the geometric means of the bin edges
std::vector<double> expected = { std::pow( 10.0, 0.5 ), std::pow( 10.0, 1.5 ), std::pow( 10.0, 2.5 ) };
ASSERT_EQ( expected.size(), xValues.size() );
for ( size_t i = 0; i < expected.size(); i++ )
{
EXPECT_NEAR( expected[i], xValues[i], 1e-9 );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimHistogramDataSourceTest, ComputeBinRange )
{
using BinRangeMode = RimHistogramDataSource::BinRangeMode;
using BinningMode = RigHistogramCalculator::BinningMode;
const double smallestPositive = 0.01;
{
auto [min, max] =
RimHistogramDataSource::computeBinRange( BinRangeMode::AUTOMATIC, 5.0, 6.0, -2.0, 100.0, BinningMode::LINEAR, smallestPositive );
EXPECT_DOUBLE_EQ( -2.0, min );
EXPECT_DOUBLE_EQ( 100.0, max );
}
{
auto [min, max] =
RimHistogramDataSource::computeBinRange( BinRangeMode::USER_DEFINED, 5.0, 6.0, -2.0, 100.0, BinningMode::LINEAR, smallestPositive );
EXPECT_DOUBLE_EQ( 5.0, min );
EXPECT_DOUBLE_EQ( 6.0, max );
}
{
// A non-positive minimum is replaced by the smallest positive value for logarithmic binning
auto [min, max] =
RimHistogramDataSource::computeBinRange( BinRangeMode::AUTOMATIC, 5.0, 6.0, -2.0, 100.0, BinningMode::LOGARITHMIC, smallestPositive );
EXPECT_DOUBLE_EQ( smallestPositive, min );
EXPECT_DOUBLE_EQ( 100.0, max );
}
{
auto [min, max] =
RimHistogramDataSource::computeBinRange( BinRangeMode::USER_DEFINED, 0.0, 6.0, -2.0, 100.0, BinningMode::LOGARITHMIC, smallestPositive );
EXPECT_DOUBLE_EQ( smallestPositive, min );
EXPECT_DOUBLE_EQ( 6.0, max );
}
{
// A positive minimum is used unchanged for logarithmic binning
auto [min, max] =
RimHistogramDataSource::computeBinRange( BinRangeMode::USER_DEFINED, 5.0, 6.0, -2.0, 100.0, BinningMode::LOGARITHMIC, smallestPositive );
EXPECT_DOUBLE_EQ( 5.0, min );
EXPECT_DOUBLE_EQ( 6.0, max );
}
}