///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2025 Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RimGridStatisticsHistogramDataSource.h" #include "RiaResultNames.h" #include "Histogram/RimHistogramPlot.h" #include "RimEclipseCase.h" #include "RimEclipseCellColors.h" #include "RimEclipseResultDefinition.h" #include "RimEclipseView.h" #include "RimHistogramCalculator.h" #include "RimProject.h" #include "RimTools.h" #include "cafPdmUiComboBoxEditor.h" CAF_PDM_XML_SOURCE_INIT( RimGridStatisticsHistogramDataSource, "GridStatisticsHistogramDataSource" ); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimGridStatisticsHistogramDataSource::RimGridStatisticsHistogramDataSource() { CAF_PDM_InitObject( "Ensemble Parameter Histogram Data Source", ); CAF_PDM_InitFieldNoDefault( &m_case, "Case", "Case" ); m_case.uiCapability()->setUiTreeChildrenHidden( true ); CAF_PDM_InitField( &m_timeStep, "TimeStep", -1, "Time Step" ); m_timeStep.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_cellFilterView, "VisibleCellView", "Filter by 3d View Visibility" ); CAF_PDM_InitFieldNoDefault( &m_property, "Property", "Property" ); m_property = new RimEclipseResultDefinition( caf::PdmUiItemInfo::LabelPosition::TOP ); m_property.uiCapability()->setUiTreeChildrenHidden( true ); m_property->setTernaryEnabled( false ); CAF_PDM_InitField( &m_numBins, "NumBins", 15, "Number of Bins" ); CAF_PDM_InitField( &m_cumulative, "Cumulative", false, "Show Cumulative Curve" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimGridStatisticsHistogramDataSource::showCumulativeCurve() const { return m_cumulative(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::setShowCumulativeCurve( bool showCumulativeCurve ) { m_cumulative = showCumulativeCurve; updateConnectedEditors(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimGridStatisticsHistogramDataSource::~RimGridStatisticsHistogramDataSource() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimGridStatisticsHistogramDataSource::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options; if ( fieldNeedingOptions == &m_case ) { RimTools::eclipseCaseOptionItems( &options ); if ( options.empty() ) { options.push_front( caf::PdmOptionItemInfo( "None", nullptr ) ); } } else if ( fieldNeedingOptions == &m_timeStep ) { options.push_back( caf::PdmOptionItemInfo( "All Time Steps", -1 ) ); RimTools::timeStepsForCase( m_case, &options ); } else if ( fieldNeedingOptions == &m_cellFilterView ) { if ( RimEclipseCase* eclipseCase = dynamic_cast( m_case() ) ) { options.push_back( caf::PdmOptionItemInfo( "Disabled", nullptr ) ); for ( RimEclipseView* view : eclipseCase->reservoirViews() ) { CAF_ASSERT( view && "Really always should have a valid view pointer in ReservoirViews" ); options.push_back( caf::PdmOptionItemInfo( view->name(), view, false, view->uiIconProvider() ) ); } } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource ::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { uiOrdering.add( &m_case ); if ( m_case ) { uiOrdering.add( &m_timeStep ); uiOrdering.add( &m_cellFilterView ); uiOrdering.add( &m_cumulative ); caf::PdmUiGroup* propertyGroup = uiOrdering.addNewGroup( "Property" ); m_property->uiOrdering( uiConfigName, *propertyGroup ); } appendBinningUiOrdering( uiOrdering, &m_numBins ); uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimHistogramDataSource::fieldChangedByUi( changedField, oldValue, newValue ); if ( changedField == &m_case ) { if ( RimEclipseCase* eclipseCase = dynamic_cast( m_case.value() ) ) { m_property->setEclipseCase( eclipseCase ); m_property->updateConnectedEditors(); dataSourceChanged.send(); } } else if ( changedField == &m_cumulative ) { // Handled by the owning curve, which creates or deletes the cumulative companion curve. // Not sent as dataSourceChanged: the companion curve observes that signal, and must not be // deleted while it is being emitted. cumulativeChanged.send(); } else { dataSourceChanged.send(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::string RimGridStatisticsHistogramDataSource::unitNameY() const { return ""; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::string RimGridStatisticsHistogramDataSource::unitNameX() const { return ""; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimHistogramDataSource::HistogramResult RimGridStatisticsHistogramDataSource::compute( RimHistogramPlot::GraphType graphType, RimHistogramPlot::FrequencyType frequencyType, bool cumulative ) const { RimHistogramDataSource::HistogramResult result; RigHistogramData histogramData = createStatisticsData(); if ( !histogramData.isHistogramVectorValid() ) return result; double min = histogramData.min; double max = histogramData.max; result.valuesX = computeHistogramBins( min, max, m_numBins, graphType, cumulative, binningMode() ); result.valuesY = computeHistogramFrequencies( histogramData.histogram, graphType, frequencyType, cumulative ); result.p10 = histogramData.p10; result.mean = histogramData.mean; result.p90 = histogramData.p90; return result; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigHistogramData RimGridStatisticsHistogramDataSource::createStatisticsData() const { RimHistogramCalculator histogramCalculator; histogramCalculator.setNumBins( static_cast( m_numBins() ) ); std::optional> customBinRange; if ( useUserDefinedBinRange() ) customBinRange = std::make_pair( m_binRangeMin(), m_binRangeMax() ); histogramCalculator.setBinningParameters( binningMode(), outOfRangeHandling(), customBinRange ); return statisticsData( histogramCalculator ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigHistogramData RimGridStatisticsHistogramDataSource::statisticsData( RimHistogramCalculator& histogramCalculator ) const { RimHistogramCalculator::StatisticsCellRangeType cellRange = RimHistogramCalculator::StatisticsCellRangeType::ALL_CELLS; RimHistogramCalculator::StatisticsTimeRangeType timeRange = RimHistogramCalculator::StatisticsTimeRangeType::ALL_TIMESTEPS; int timeStep = 0; if ( m_timeStep() != -1 && !m_property()->hasStaticResult() ) { timeStep = m_timeStep(); timeRange = RimHistogramCalculator::StatisticsTimeRangeType::CURRENT_TIMESTEP; } if ( m_cellFilterView.value() ) { // Filter by visible cells of the view cellRange = RimHistogramCalculator::StatisticsCellRangeType::VISIBLE_CELLS; RimEclipseView* eclipseView = dynamic_cast( m_cellFilterView.value() ); return histogramCalculator.histogramData( eclipseView, m_property.value(), cellRange, timeRange, timeStep ); } else { RimEclipseView* eclipseView = nullptr; return histogramCalculator.histogramData( eclipseView, m_property.value(), cellRange, timeRange, timeStep ); } } //-------------------------------------------------------------------------------------------------- /// The range of the data selected for the histogram: the statistics are computed without custom /// binning, where min and max always are the data range. //-------------------------------------------------------------------------------------------------- std::optional> RimGridStatisticsHistogramDataSource::dataRange() const { RimHistogramCalculator histogramCalculator; RigHistogramData histogramData = statisticsData( histogramCalculator ); if ( !RigStatisticsTools::isValidNumber( histogramData.min ) || !RigStatisticsTools::isValidNumber( histogramData.max ) ) return std::nullopt; return std::make_pair( histogramData.min, histogramData.max ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::string RimGridStatisticsHistogramDataSource::name() const { if ( m_case() == nullptr ) return "Undefined"; QStringList nameTags; nameTags += m_property()->resultVariable(); nameTags += m_case()->caseUserDescription(); auto createTimeStepString = [this]() -> QString { if ( m_case() && m_property->hasDynamicResult() ) { if ( m_timeStep == -1 ) return "All Time Steps"; // The stored time step can be out of range if the case has been replaced by a case with fewer time steps const QStringList timeStepNames = m_case->timeStepStrings(); if ( m_timeStep() >= 0 && m_timeStep() < timeStepNames.size() ) return timeStepNames[m_timeStep]; } return ""; }; QString timeStepStr = createTimeStepString(); if ( !timeStepStr.isEmpty() ) { nameTags += timeStepStr; } return nameTags.join( ", " ).toStdString(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimGridStatisticsHistogramDataSource::filterDescriptions() const { std::vector descriptions; if ( m_cellFilterView() ) descriptions.push_back( "Filter: Visible cells in 3D view" ); auto baseDescriptions = RimHistogramDataSource::filterDescriptions(); descriptions.insert( descriptions.end(), baseDescriptions.begin(), baseDescriptions.end() ); return descriptions; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource ::initAfterRead() { RimEclipseCase* eclipseCase = dynamic_cast( m_case() ); if ( eclipseCase ) { m_property->setEclipseCase( eclipseCase ); } // A binning mode stored in the project file is a user choice: only property changes made after // the project was loaded should enable logarithmic binning. m_previousResultVariable = m_property->resultVariable(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::setDefaults() { RimProject* project = RimProject::current(); if ( project ) { if ( !project->eclipseCases().empty() ) { RimEclipseCase* eclipseCase = project->eclipseCases().front(); m_case = eclipseCase; m_property->setEclipseCase( eclipseCase ); m_property->setResultType( RiaDefines::ResultCatType::STATIC_NATIVE ); m_property->setResultVariable( "PORO" ); if ( eclipseCase && !eclipseCase->reservoirViews().empty() ) { m_cellFilterView.setValue( eclipseCase->reservoirViews().front() ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::cellFilterViewUpdated() { dataSourceChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::loadDataAndUpdate() { updateBinningOnPropertyChange(); dataSourceChanged.send(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigHistogramCalculator::BinningMode RimGridStatisticsHistogramDataSource::binningModeForResult( const QString& resultVariable ) { return RiaResultNames::isLogarithmicResult( resultVariable ) ? RigHistogramCalculator::BinningMode::LOGARITHMIC : RigHistogramCalculator::BinningMode::LINEAR; } //-------------------------------------------------------------------------------------------------- /// The binning follows the selected property: logarithmic results are best viewed with logarithmic /// binning, others with linear binning, and a user-defined bin range set up for one result does not /// apply to the value range of another. Only an actual property change updates the binning: the /// user stays in control of the settings afterwards. //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::updateBinningOnPropertyChange() { const QString resultVariable = m_property()->resultVariable(); if ( resultVariable != m_previousResultVariable ) { setBinningMode( binningModeForResult( resultVariable ) ); resetBinRange(); m_previousResultVariable = resultVariable; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridStatisticsHistogramDataSource::setPropertiesFromView( RimEclipseView* view ) { CAF_ASSERT( view ); m_case = view->ownerCase(); RimEclipseCase* eclipseCase = dynamic_cast( m_case.value() ); if ( eclipseCase ) m_property->setEclipseCase( eclipseCase ); const RimEclipseResultDefinition* resDef = dynamic_cast( view->cellResult() ); if ( resDef ) m_property->simpleCopy( resDef ); updateBinningOnPropertyChange(); dataSourceChanged.send(); }