#14766 Fix potentially uninitialised object used for accumulation

RigStatisticsCalculator::mobileVolumeWeightedMean() accumulated into a sum
from an out-parameter that callees were not required to write. Every
implementation of the per time step overload has early-return paths that
leave the value untouched, as does RigWeightedMeanCalc::weightedMeanOverCells,
so indeterminate stack memory could be summed and presented in the UI.

Return std::optional<double> from the computation layer instead of using an
out-parameter, making "value not available" explicit and impossible to ignore:

- RigWeightedMeanCalc::weightedMeanOverCells
- RigStatisticsCalculator::mobileVolumeWeightedMean and all overrides

Time steps without a computable mean no longer contribute to the accumulated
sum, and are excluded from the divisor. Previously they would either add
garbage or, with a naive zero initialisation, bias the average towards zero.

RigStatisticsDataCache keeps the existing HUGE_VAL marker on its public API
via value_or(), so behaviour seen by RigHistogramData and the overlay info
is unchanged apart from the fix.

Also:
- Set m_isVolumeWeightedMeanCalculated in the per time step cache, which was
  never assigned, so the cached value was recomputed on every call.
- Remove a duplicated ensureKnownResultLoaded() call in
  RigEclipseNativeVisibleCellsStatCalc.

Fixes #14766
This commit is contained in:
Kristian Bendiksen committed 2026-09-21 18:36:27 +02:00
1 parent 0eba9e8ef7
commit 4d2a868079
16 files changed
+149 -114

No files matched your search

@@ -98,6 +98,7 @@ size_t RigEclipseAllanFaultsStatCalc::timeStepCount()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigEclipseAllanFaultsStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, double& result )
std::optional<double> RigEclipseAllanFaultsStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex )
{
return {};
}
@@ -34,13 +34,13 @@ class RigEclipseAllanFaultsStatCalc : public RigStatisticsCalculator
public:
RigEclipseAllanFaultsStatCalc( RigNNCData* cellResultsData, const RigEclipseResultAddress& scalarResultIndex );
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
void mobileVolumeWeightedMean( size_t timeStepIndex, double& result ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex ) override;
private:
RigNNCData* m_caseData;
@@ -160,7 +160,7 @@ size_t RigEclipseNativeStatCalc::timeStepCount()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigEclipseNativeStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, double& mean )
std::optional<double> RigEclipseNativeStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex )
{
RigEclipseResultAddress mobPorvAddress( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::mobilePoreVolumeName() );
@@ -168,7 +168,7 @@ void RigEclipseNativeStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, d
// RigCaseCellResultsData::createPlaceholderResultEntries has not been executed
if ( !m_resultsData->ensureKnownResultLoaded( mobPorvAddress ) )
{
return;
return {};
}
const std::vector<double>& weights = m_resultsData->cellScalarResults( mobPorvAddress, 0 );
@@ -176,11 +176,10 @@ void RigEclipseNativeStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, d
const RigActiveCellInfo* actCellInfo = m_resultsData->activeCellInfo();
RigWeightedMeanCalc::weightedMeanOverCells( &weights,
&values,
nullptr,
false,
actCellInfo,
m_resultsData->isUsingGlobalActiveIndex( m_eclipseResultAddress ),
&mean );
return RigWeightedMeanCalc::weightedMeanOverCells( &weights,
&values,
nullptr,
false,
actCellInfo,
m_resultsData->isUsingGlobalActiveIndex( m_eclipseResultAddress ) );
}
@@ -37,16 +37,16 @@ class RigEclipseNativeStatCalc : public RigStatisticsCalculator
public:
RigEclipseNativeStatCalc( RigCaseCellResultsData* cellResultsData, const RigEclipseResultAddress& eclipseResultAddress );
bool hasPreciseP10p90() const override;
void p10p90CellScalarValues( double& min, double& max ) override;
void p10p90CellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
void mobileVolumeWeightedMean( size_t timeStepIndex, double& mean ) override;
bool hasPreciseP10p90() const override;
void p10p90CellScalarValues( double& min, double& max ) override;
void p10p90CellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex ) override;
private:
RigCaseCellResultsData* m_resultsData;
@@ -145,9 +145,9 @@ size_t RigEclipseNativeVisibleCellsStatCalc::timeStepCount()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigEclipseNativeVisibleCellsStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, double& result )
std::optional<double> RigEclipseNativeVisibleCellsStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex )
{
if ( !m_caseData ) return;
if ( !m_caseData ) return {};
RigEclipseResultAddress mobPorvAddress( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::mobilePoreVolumeName() );
@@ -155,24 +155,21 @@ void RigEclipseNativeVisibleCellsStatCalc::mobileVolumeWeightedMean( size_t time
// RigCaseCellResultsData::createPlaceholderResultEntries has not been executed
if ( !m_caseData->ensureKnownResultLoaded( mobPorvAddress ) )
{
return;
return {};
}
m_caseData->ensureKnownResultLoaded( mobPorvAddress );
if ( !m_caseData->hasResultEntry( m_resultAddress ) ) return;
if ( m_caseData->timeStepCount( m_resultAddress ) == 0 ) return;
if ( !m_caseData->hasResultEntry( m_resultAddress ) ) return {};
if ( m_caseData->timeStepCount( m_resultAddress ) == 0 ) return {};
const std::vector<double>& weights = m_caseData->cellScalarResults( mobPorvAddress, 0 );
const std::vector<double>& values = m_caseData->cellScalarResults( m_resultAddress, timeStepIndex );
const RigActiveCellInfo* actCellInfo = m_caseData->activeCellInfo();
RigWeightedMeanCalc::weightedMeanOverCells( &weights,
&values,
m_cellVisibilities.p(),
true,
actCellInfo,
m_caseData->isUsingGlobalActiveIndex( m_resultAddress ),
&result );
return RigWeightedMeanCalc::weightedMeanOverCells( &weights,
&values,
m_cellVisibilities.p(),
true,
actCellInfo,
m_caseData->isUsingGlobalActiveIndex( m_resultAddress ) );
}
@@ -39,16 +39,16 @@ public:
const RigEclipseResultAddress& scalarResultIndex,
const cvf::UByteArray* cellVisibilities );
bool hasPreciseP10p90() const override;
void p10p90CellScalarValues( double& min, double& max ) override;
void p10p90CellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
void mobileVolumeWeightedMean( size_t timeStepIndex, double& result ) override;
bool hasPreciseP10p90() const override;
void p10p90CellScalarValues( double& min, double& max ) override;
void p10p90CellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex ) override;
private:
RigCaseCellResultsData* m_caseData;
@@ -115,10 +115,10 @@ size_t RigFlowDiagStatCalc::timeStepCount()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigFlowDiagStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, double& mean )
std::optional<double> RigFlowDiagStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex )
{
auto eclCase = m_resultsData->flowDiagSolution()->firstAncestorOrThisOfType<RimEclipseResultCase>();
if ( !eclCase ) return;
if ( !eclCase ) return {};
RigCaseCellResultsData* caseCellResultsData = eclCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
RigEclipseResultAddress mobPoreVolResAddr( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::mobilePoreVolumeName() );
@@ -130,5 +130,5 @@ void RigFlowDiagStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, double
const RigActiveCellInfo* actCellInfo = m_resultsData->activeCellInfo( m_resVarAddr );
RigWeightedMeanCalc::weightedMeanOverCells( &weights, values, nullptr, false, actCellInfo, true, &mean );
return RigWeightedMeanCalc::weightedMeanOverCells( &weights, values, nullptr, false, actCellInfo, true );
}
@@ -34,13 +34,13 @@ class RigFlowDiagStatCalc : public RigStatisticsCalculator
public:
RigFlowDiagStatCalc( RigFlowDiagResults* flowDiagResults, const RigFlowDiagResultAddress& resVarAddr );
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
void mobileVolumeWeightedMean( size_t timeStepIndex, double& mean ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex ) override;
private:
RigFlowDiagResults* m_resultsData;
@@ -105,10 +105,10 @@ size_t RigFlowDiagVisibleCellsStatCalc::timeStepCount()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigFlowDiagVisibleCellsStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex, double& result )
std::optional<double> RigFlowDiagVisibleCellsStatCalc::mobileVolumeWeightedMean( size_t timeStepIndex )
{
auto eclCase = m_resultsData->flowDiagSolution()->firstAncestorOrThisOfType<RimEclipseResultCase>();
if ( !eclCase ) return;
if ( !eclCase ) return {};
RigCaseCellResultsData* caseCellResultsData = eclCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
@@ -120,5 +120,5 @@ void RigFlowDiagVisibleCellsStatCalc::mobileVolumeWeightedMean( size_t timeStepI
const RigActiveCellInfo* actCellInfo = m_resultsData->activeCellInfo( m_resVarAddr );
RigWeightedMeanCalc::weightedMeanOverCells( &weights, values, m_cellVisibilities.p(), true, actCellInfo, true, &result );
return RigWeightedMeanCalc::weightedMeanOverCells( &weights, values, m_cellVisibilities.p(), true, actCellInfo, true );
}
@@ -41,13 +41,13 @@ public:
const RigFlowDiagResultAddress& resVarAddr,
const cvf::UByteArray* cellVisibilities );
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
void mobileVolumeWeightedMean( size_t timeStepIndex, double& result ) override;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override;
void posNegClosestToZero( size_t timeStepIndex, double& pos, double& neg ) override;
void valueSumAndSampleCount( size_t timeStepIndex, double& valueSum, size_t& sampleCount ) override;
void addDataToHistogramCalculator( size_t timeStepIndex, RigHistogramCalculator& histogramCalculator ) override;
void uniqueValues( size_t timeStepIndex, std::set<int>& values ) override;
size_t timeStepCount() override;
std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex ) override;
private:
RigFlowDiagResults* m_resultsData;
@@ -28,19 +28,18 @@
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigWeightedMeanCalc::weightedMeanOverCells( const std::vector<double>* weights,
const std::vector<double>* values,
const cvf::UByteArray* cellVisibilities,
bool isUsingVisibleCells,
const RigActiveCellInfo* actCellInfo,
bool isUsingActiveIndex,
double* result )
std::optional<double> RigWeightedMeanCalc::weightedMeanOverCells( const std::vector<double>* weights,
const std::vector<double>* values,
const cvf::UByteArray* cellVisibilities,
bool isUsingVisibleCells,
const RigActiveCellInfo* actCellInfo,
bool isUsingActiveIndex )
{
if ( !( weights && values && result ) ) return;
if ( !cellVisibilities && isUsingVisibleCells ) return;
if ( !actCellInfo && isUsingActiveIndex ) return;
if ( !( weights && values ) ) return {};
if ( !cellVisibilities && isUsingVisibleCells ) return {};
if ( !actCellInfo && isUsingActiveIndex ) return {};
if ( weights->empty() || values->empty() ) return;
if ( weights->empty() || values->empty() ) return {};
double weightedSum = 0.0;
double weightSum = 0.0;
@@ -80,12 +79,7 @@ void RigWeightedMeanCalc::weightedMeanOverCells( const std::vector<double>* weig
weightSum += weight;
}
if ( weightSum != 0 )
{
*result = weightedSum / weightSum;
}
else
{
*result = HUGE_VAL;
}
if ( weightSum == 0.0 ) return {};
return weightedSum / weightSum;
}
@@ -20,6 +20,7 @@
#include "cvfArray.h"
#include <optional>
#include <vector>
class RigActiveCellInfo;
@@ -27,11 +28,12 @@ class RigActiveCellInfo;
class RigWeightedMeanCalc
{
public:
static void weightedMeanOverCells( const std::vector<double>* weights,
const std::vector<double>* values,
const cvf::UByteArray* cellVisibilities,
bool isUsingVisibleCells,
const RigActiveCellInfo* actCellInfo,
bool isUsingActiveIndex,
double* result );
// Returns the weighted mean over the given cells, or an empty optional if the mean can not be computed,
// ie. when required input data is missing or the sum of weights is zero.
static std::optional<double> weightedMeanOverCells( const std::vector<double>* weights,
const std::vector<double>* values,
const cvf::UByteArray* cellVisibilities,
bool isUsingVisibleCells,
const RigActiveCellInfo* actCellInfo,
bool isUsingActiveIndex );
};
@@ -89,27 +89,34 @@ void RigStatisticsCalculator::addDataToHistogramCalculator( RigHistogramCalculat
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigStatisticsCalculator::mobileVolumeWeightedMean( size_t timeStepIndex, double& mean )
std::optional<double> RigStatisticsCalculator::mobileVolumeWeightedMean( size_t timeStepIndex )
{
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigStatisticsCalculator::mobileVolumeWeightedMean( double& mean )
std::optional<double> RigStatisticsCalculator::mobileVolumeWeightedMean()
{
double sum = 0.0;
size_t tsCount = timeStepCount();
double sum = 0.0;
size_t contributingTsCount = 0;
const size_t tsCount = timeStepCount();
for ( size_t tIdx = 0; tIdx < tsCount; tIdx++ )
{
double meanForTimeStep;
mobileVolumeWeightedMean( tIdx, meanForTimeStep );
sum += meanForTimeStep;
}
if ( tsCount != 0 )
{
mean = sum / tsCount;
// Time steps without a computable mean, ie. when the mobile pore volume is missing, are not
// allowed to contribute to the accumulated sum
if ( const auto meanForTimeStep = mobileVolumeWeightedMean( tIdx ) )
{
sum += *meanForTimeStep;
contributingTsCount++;
}
}
if ( contributingTsCount == 0 ) return {};
return sum / contributingTsCount;
}
//--------------------------------------------------------------------------------------------------
@@ -22,6 +22,7 @@
#include "cvfCollection.h"
#include "cvfObject.h"
#include <optional>
#include <vector>
class RigHistogramCalculator;
@@ -52,8 +53,10 @@ public:
virtual size_t timeStepCount() = 0;
void mobileVolumeWeightedMean( double& mean );
virtual void mobileVolumeWeightedMean( size_t timeStepIndex, double& mean );
// Returns the mobile volume weighted mean, or an empty optional if it can not be computed, ie. when the
// mobile pore volume result is not available. Implementations must not return a value in that case.
std::optional<double> mobileVolumeWeightedMean();
virtual std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex );
static void posNegClosestToZero( const std::vector<double>& values, double& pos, double& neg );
};
@@ -331,7 +331,11 @@ void RigStatisticsDataCache::mobileVolumeWeightedMean( size_t timeStepIndex, dou
if ( !m_statsPrTs[timeStepIndex].m_isVolumeWeightedMeanCalculated )
{
m_statisticsCalculator->mobileVolumeWeightedMean( timeStepIndex, m_statsPrTs[timeStepIndex].m_volumeWeightedMean );
// HUGE_VAL is the "not available" marker used by the public statistics API
m_statsPrTs[timeStepIndex].m_volumeWeightedMean =
m_statisticsCalculator->mobileVolumeWeightedMean( timeStepIndex ).value_or( HUGE_VAL );
m_statsPrTs[timeStepIndex].m_isVolumeWeightedMeanCalculated = true;
}
mean = m_statsPrTs[timeStepIndex].m_volumeWeightedMean;
@@ -344,7 +348,7 @@ void RigStatisticsDataCache::mobileVolumeWeightedMean( double& mean )
{
if ( !m_statsAllTimesteps.m_isVolumeWeightedMeanCalculated )
{
m_statisticsCalculator->mobileVolumeWeightedMean( m_statsAllTimesteps.m_volumeWeightedMean );
m_statsAllTimesteps.m_volumeWeightedMean = m_statisticsCalculator->mobileVolumeWeightedMean().value_or( HUGE_VAL );
m_statsAllTimesteps.m_isVolumeWeightedMeanCalculated = true;
}
@@ -28,6 +28,7 @@
#include <cmath>
#include <limits>
#include <numeric>
#include <optional>
//--------------------------------------------------------------------------------------------------
///
@@ -586,11 +587,15 @@ namespace
class VectorBackedStatisticsCalculator : public RigStatisticsCalculator
{
public:
explicit VectorBackedStatisticsCalculator( const std::vector<std::vector<double>>& valuesPerTimeStep )
explicit VectorBackedStatisticsCalculator( const std::vector<std::vector<double>>& valuesPerTimeStep,
const std::vector<std::optional<double>>& mobileVolumeWeightedMeans = {} )
: m_valuesPerTimeStep( valuesPerTimeStep )
, m_mobileVolumeWeightedMeans( mobileVolumeWeightedMeans )
{
}
using RigStatisticsCalculator::mobileVolumeWeightedMean;
void minMaxCellScalarValues( size_t timeStepIndex, double& min, double& max ) override
{
MinMaxAccumulator accumulator( min, max );
@@ -624,11 +629,34 @@ public:
size_t timeStepCount() override { return m_valuesPerTimeStep.size(); }
std::optional<double> mobileVolumeWeightedMean( size_t timeStepIndex ) override
{
if ( timeStepIndex >= m_mobileVolumeWeightedMeans.size() ) return {};
return m_mobileVolumeWeightedMeans[timeStepIndex];
}
private:
std::vector<std::vector<double>> m_valuesPerTimeStep;
std::vector<std::vector<double>> m_valuesPerTimeStep;
std::vector<std::optional<double>> m_mobileVolumeWeightedMeans;
};
} // namespace
//--------------------------------------------------------------------------------------------------
/// Unavailable time steps must not contribute to either the accumulated sum or the divisor.
//--------------------------------------------------------------------------------------------------
TEST( RigStatisticsCalculator, MobileVolumeWeightedMeanSkipsUnavailableTimeSteps )
{
VectorBackedStatisticsCalculator partlyAvailable( { {}, {}, {} }, { 10.0, std::nullopt, 20.0 } );
const auto mean = partlyAvailable.mobileVolumeWeightedMean();
ASSERT_TRUE( mean.has_value() );
EXPECT_DOUBLE_EQ( 15.0, *mean );
VectorBackedStatisticsCalculator unavailable( { {}, {} }, { std::nullopt, std::nullopt } );
EXPECT_FALSE( unavailable.mobileVolumeWeightedMean().has_value() );
}
//--------------------------------------------------------------------------------------------------
/// computeHistogram() fills a custom-configured histogram calculator without disturbing the
/// statistics cached for other consumers