///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2023 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 "RimSummaryRegressionAnalysisCurve.h" #include "RiaLogging.h" #include "RiaQDateTimeTools.h" #include "RiaRegressionTextTools.h" #include "RiaStatisticsTools.h" #include "RiaTimeTTools.h" #include "RimEnsembleCurveSet.h" #include "RimEnsembleCurveSetCollection.h" #include "RimEnsembleStatistics.h" #include "RimSummaryCaseCollection.h" #include "RimSummaryPlot.h" #include "RimTimeAxisAnnotation.h" #include "cafPdmUiDateEditor.h" #include "cafPdmUiLineEditor.h" #include "cafPdmUiSliderEditor.h" #include "cafPdmUiSliderTools.h" #include "cafPdmUiTextEditor.h" #include "ExponentialRegression.hpp" #include "LinearRegression.hpp" #include "LogarithmicRegression.hpp" #include "LogisticRegression.hpp" #include "PolynomialRegression.hpp" #include "PowerFitRegression.hpp" #include #include #include CAF_PDM_SOURCE_INIT( RimSummaryRegressionAnalysisCurve, "RegressionAnalysisCurve" ); namespace caf { template <> void caf::AppEnum::setUp() { addItem( RimSummaryRegressionAnalysisCurve::RegressionType::LINEAR, "LINEAR", "Linear" ); addItem( RimSummaryRegressionAnalysisCurve::RegressionType::POLYNOMIAL, "POLYNOMIAL", "Polynomial" ); addItem( RimSummaryRegressionAnalysisCurve::RegressionType::POWER_FIT, "POWER_FIT", "Power Fit" ); addItem( RimSummaryRegressionAnalysisCurve::RegressionType::EXPONENTIAL, "EXPONENTIAL", "Exponential" ); addItem( RimSummaryRegressionAnalysisCurve::RegressionType::LOGARITHMIC, "LOGARITHMIC", "Logarithmic" ); setDefault( RimSummaryRegressionAnalysisCurve::RegressionType::POLYNOMIAL ); } template <> void caf::AppEnum::setUp() { addItem( RimSummaryRegressionAnalysisCurve::ForecastUnit::DAYS, "DAYS", "Days" ); addItem( RimSummaryRegressionAnalysisCurve::ForecastUnit::MONTHS, "MONTHS", "Months" ); addItem( RimSummaryRegressionAnalysisCurve::ForecastUnit::YEARS, "YEARS", "Years" ); setDefault( RimSummaryRegressionAnalysisCurve::ForecastUnit::YEARS ); } template <> void caf::AppEnum::setUp() { addItem( RimSummaryRegressionAnalysisCurve::DataSource::SUMMARY_ADDRESS, "SUMMARY_ADDRESS", "Summary Address" ); addItem( RimSummaryRegressionAnalysisCurve::DataSource::ENSEMBLE, "ENSEMBLE", "Ensemble" ); setDefault( RimSummaryRegressionAnalysisCurve::DataSource::SUMMARY_ADDRESS ); } template <> void caf::AppEnum::setUp() { addItem( RimSummaryRegressionAnalysisCurve::RangeType::FULL_RANGE, "FULL_RANGE", "Full Range" ); addItem( RimSummaryRegressionAnalysisCurve::RangeType::USER_DEFINED_RANGE, "USER_DEFINED_RANGE", "Custom Range" ); setDefault( RimSummaryRegressionAnalysisCurve::RangeType::FULL_RANGE ); } }; // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryRegressionAnalysisCurve::RimSummaryRegressionAnalysisCurve() { CAF_PDM_InitObject( "Regression Analysis Curve", ":/regression-curve.svg" ); CAF_PDM_InitFieldNoDefault( &m_dataSourceForRegression, "DataSourceForRegression", "Data Source" ); CAF_PDM_InitFieldNoDefault( &m_ensembleCurveSet, "SourceCurveSet", "Source Curve Set" ); CAF_PDM_InitFieldNoDefault( &m_ensembleStatisticsType, "EnsembleStatisticsType", "Ensemble Statistics Type" ); CAF_PDM_InitFieldNoDefault( &m_regressionType, "RegressionType", "Type" ); CAF_PDM_InitField( &m_forecastForward, "ForecastForward", 0, "Forward" ); CAF_PDM_InitField( &m_forecastBackward, "ForecastBackward", 0, "Backward" ); CAF_PDM_InitFieldNoDefault( &m_forecastUnit, "ForecastUnit", "Unit" ); CAF_PDM_InitField( &m_polynomialDegree, "PolynomialDegree", 3, "Degree" ); CAF_PDM_InitFieldNoDefault( &m_timeRangeSelection, "TimeRangeSelection", "Time Range" ); CAF_PDM_InitFieldNoDefault( &m_minTimeSliderPosition, "MinTimeSliderPosition", "From" ); m_minTimeSliderPosition.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_maxTimeSliderPosition, "MaxTimeSliderPosition", "To" ); m_maxTimeSliderPosition.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); CAF_PDM_InitField( &m_showTimeSelectionInPlot, "ShowTimeSelectionInPlot", false, "Show In Plot" ); CAF_PDM_InitFieldNoDefault( &m_expressionText, "ExpressionText", "Expression" ); m_expressionText.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() ); m_expressionText.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); m_expressionText.uiCapability()->setUiReadOnly( true ); m_expressionText.xmlCapability()->disableIO(); CAF_PDM_InitFieldNoDefault( &m_xRangeSelection, "XRangeSelection", "X Value Range" ); CAF_PDM_InitField( &m_valueRangeX, "ValueRangeX", std::make_pair( 0.0, 0.0 ), "Value Range X" ); m_valueRangeX.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); CAF_PDM_InitFieldNoDefault( &m_yRangeSelection, "YRangeSelection", "Y Value Range" ); CAF_PDM_InitField( &m_valueRangeY, "ValueRangeY", std::make_pair( 0.0, 0.0 ), "Value Range Y" ); m_valueRangeY.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimSummaryRegressionAnalysisCurve::~RimSummaryRegressionAnalysisCurve() { auto plot = firstAncestorOrThisOfType(); if ( plot && m_timeRangeAnnotation ) plot->removeTimeAnnotation( m_timeRangeAnnotation ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::setEnsembleCurveSet( RimEnsembleCurveSet* ensembleCurveSet ) { m_dataSourceForRegression = DataSource::ENSEMBLE; m_ensembleCurveSet = ensembleCurveSet; m_ensembleStatisticsType = RifEclipseSummaryAddressDefines::StatisticsType::P10; setSummaryAddressY( {} ); setSummaryAddressX( {} ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::onLoadDataAndUpdate( bool updateParentPlot ) { extractSourceCurveData(); updateDefaultValues(); // Clear derived data m_valuesX.clear(); m_valuesY.clear(); m_timeStepsX.clear(); m_timeStepsY.clear(); m_expressionText = "Undefined"; std::vector xValues = m_sourceValuesX; std::vector yValues = m_sourceValuesY; std::vector timeStepsX = m_sourceTimeStepsX; std::vector timeStepsY = m_sourceTimeStepsY; QStringList errorMessages; if ( yValues.empty() ) { errorMessages += "No Y values found for regression curve."; } if ( axisTypeX() == RiaDefines::HorizontalAxisType::SUMMARY_VECTOR ) { if ( xValues.size() != yValues.size() ) errorMessages += "X value count and Y value count differs."; if ( xValues.size() != timeStepsX.size() ) errorMessages += "X value count and X time step count differs."; if ( xValues.size() != timeStepsY.size() ) errorMessages += "X value count and Y time step count differs."; if ( timeStepsX != timeStepsY ) { errorMessages += "Differences in time steps for X and Y axis detected. This is currently not supported. Make sure that the same " "case is used for both axis."; } } if ( !errorMessages.isEmpty() ) { // Call parent class to make sure the curve is removed from the plot RimSummaryCurve::onLoadDataAndUpdate( updateParentPlot ); QString errMsg = errorMessages.join( "\n" ); RiaLogging::error( errMsg ); return; } std::vector indicesToRemove; if ( axisTypeX() == RiaDefines::HorizontalAxisType::SUMMARY_VECTOR ) { for ( size_t i = 0; i < xValues.size(); i++ ) { if ( xValues[i] < m_valueRangeX().first || xValues[i] > m_valueRangeX().second || !RiaStatisticsTools::isValidNumber( xValues[i] ) ) { indicesToRemove.push_back( i ); } } for ( size_t i = 0; i < yValues.size(); i++ ) { if ( yValues[i] < m_valueRangeY().first || yValues[i] > m_valueRangeY().second || !RiaStatisticsTools::isValidNumber( yValues[i] ) ) { indicesToRemove.push_back( i ); } } } // Sort indices in descending order std::sort( indicesToRemove.rbegin(), indicesToRemove.rend() ); // There might be duplicates, remove them indicesToRemove.erase( std::unique( indicesToRemove.begin(), indicesToRemove.end() ), indicesToRemove.end() ); // Remove elements at the specified indices for ( auto index : indicesToRemove ) { if ( index < yValues.size() ) { yValues.erase( yValues.begin() + index ); timeStepsY.erase( timeStepsY.begin() + index ); } if ( index < xValues.size() ) { xValues.erase( xValues.begin() + index ); timeStepsX.erase( timeStepsX.begin() + index ); } } QString descriptionX; std::tie( m_timeStepsX, m_valuesX, descriptionX ) = computeRegressionCurve( timeStepsX, xValues ); QString descriptionY; std::tie( m_timeStepsY, m_valuesY, descriptionY ) = computeRegressionCurve( timeStepsY, yValues ); m_expressionText = descriptionY; RimSummaryCurve::onLoadDataAndUpdate( updateParentPlot ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::extractSourceCurveData() { std::vector xValues; std::vector yValues; std::vector xTimeSteps; std::vector yTimeSteps; if ( m_dataSourceForRegression() == DataSource::ENSEMBLE ) { auto findStatisticsCurve = []( RimEnsembleCurveSet* curveSet, const QString& statisticsCurveName ) -> RimSummaryCurve* { if ( curveSet == nullptr ) return nullptr; auto allCurves = curveSet->curves(); for ( auto curve : allCurves ) { auto yAddr = curve->summaryAddressY(); if ( yAddr.category() == RifEclipseSummaryAddressDefines::SummaryCategory::SUMMARY_ENSEMBLE_STATISTICS ) { auto statisticsName = QString::fromStdString( yAddr.ensembleStatisticsVectorName() ); if ( statisticsName == statisticsCurveName ) { return curve; } } } return nullptr; }; auto curve = findStatisticsCurve( m_ensembleCurveSet(), m_ensembleStatisticsType().uiText() ); if ( curve ) { yValues = curve->valuesY(); xValues = curve->valuesX(); auto curveTimeY = curve->timeStepsY(); if ( curveTimeY.size() == yValues.size() ) { yTimeSteps = curveTimeY; } else { // Fallback to use time steps from summary case // The time steps are used for reference, not used when computing the regression curve auto summaryCase = m_ensembleCurveSet->summaryCaseCollection()->allSummaryCases().back(); auto allTimeSteps = summaryCase->summaryReader()->timeSteps( {} ); yTimeSteps = allTimeSteps; yTimeSteps.resize( yValues.size() ); } if ( xValues.size() == yValues.size() ) xTimeSteps = yTimeSteps; } } else { // Get curve data from the summary data defined by X and Y axis data xValues = RimSummaryCurve::valuesX(); yValues = RimSummaryCurve::valuesY(); xTimeSteps = RimSummaryCurve::timeStepsX(); yTimeSteps = RimSummaryCurve::timeStepsY(); } m_sourceTimeStepsX = xTimeSteps; m_sourceTimeStepsY = yTimeSteps; m_sourceValuesX = xValues; m_sourceValuesY = yValues; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::valuesY() const { return m_valuesY; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::valuesX() const { return m_valuesX; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::timeStepsY() const { return m_timeStepsY; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::timeStepsX() const { return m_timeStepsX; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::tuple, std::vector, QString> RimSummaryRegressionAnalysisCurve::computeRegressionCurve( const std::vector& timeSteps, const std::vector& values ) const { if ( values.empty() || timeSteps.empty() ) return { timeSteps, values, "" }; auto [minTimeStep, maxTimeStep] = selectedTimeStepRange(); auto [timeStepsInRange, valuesInRange] = getInRangeValues( timeSteps, values, minTimeStep, maxTimeStep ); if ( timeStepsInRange.empty() || valuesInRange.empty() ) return {}; const std::vector timeStepsD = convertToDouble( timeStepsInRange ); // Create time steps which includes forecasting backward and forward const std::vector outputTimeSteps = getOutputTimeSteps( timeStepsInRange, m_forecastBackward(), m_forecastForward(), m_forecastUnit() ); const std::vector outputTimeStepsD = convertToDouble( outputTimeSteps ); // Move the time scale from seconds since epoch to years from first data point. // This gives better precision for the regression analysis. const double offset = timeStepsD[0]; auto convertToYearsFromFirstTimeStep = []( const std::vector& timeSteps, double offset ) { const double secondsPerYear = 60 * 60 * 24 * 365; std::vector timeStepsH( timeSteps.size() ); for ( size_t i = 0; i < timeSteps.size(); i++ ) { timeStepsH[i] = ( timeSteps[i] - offset ) / secondsPerYear; } return timeStepsH; }; const std::vector timeStepsDYears = convertToYearsFromFirstTimeStep( timeStepsD, offset ); const std::vector outputTimeStepsDYears = convertToYearsFromFirstTimeStep( outputTimeStepsD, offset ); if ( m_regressionType == RegressionType::LINEAR ) { regression::LinearRegression linearRegression; linearRegression.fit( timeStepsDYears, valuesInRange ); std::vector predictedValues = linearRegression.predict( outputTimeStepsDYears ); return { outputTimeSteps, predictedValues, generateRegressionText( linearRegression ) }; } else if ( m_regressionType == RegressionType::POLYNOMIAL ) { regression::PolynomialRegression polynomialRegression; polynomialRegression.fit( timeStepsDYears, valuesInRange, m_polynomialDegree ); std::vector predictedValues = polynomialRegression.predict( outputTimeStepsDYears ); return { outputTimeSteps, predictedValues, generateRegressionText( polynomialRegression ) }; } else if ( m_regressionType == RegressionType::POWER_FIT ) { auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsDYears, valuesInRange ); if ( filteredTimeSteps.empty() || filteredValues.empty() ) return {}; regression::PowerFitRegression powerFitRegression; powerFitRegression.fit( filteredTimeSteps, filteredValues ); std::vector predictedValues = powerFitRegression.predict( outputTimeStepsDYears ); return { outputTimeSteps, predictedValues, generateRegressionText( powerFitRegression ) }; } else if ( m_regressionType == RegressionType::EXPONENTIAL ) { auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsDYears, valuesInRange ); if ( filteredTimeSteps.empty() || filteredValues.empty() ) return {}; regression::ExponentialRegression exponentialRegression; exponentialRegression.fit( filteredTimeSteps, filteredValues ); std::vector predictedValues = exponentialRegression.predict( outputTimeStepsDYears ); return { convertToTimeT( outputTimeStepsD ), predictedValues, generateRegressionText( exponentialRegression ) }; } else if ( m_regressionType == RegressionType::LOGARITHMIC ) { auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsDYears, valuesInRange ); if ( filteredTimeSteps.empty() || filteredValues.empty() ) return {}; regression::LogarithmicRegression logarithmicRegression; logarithmicRegression.fit( filteredTimeSteps, filteredValues ); std::vector predictedValues = logarithmicRegression.predict( outputTimeStepsDYears ); return { convertToTimeT( outputTimeStepsD ), predictedValues, generateRegressionText( logarithmicRegression ) }; } return { timeSteps, values, "" }; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { RimPlotCurve::updateFieldUiState(); if ( m_dataSourceForRegression() == DataSource::ENSEMBLE ) { uiOrdering.add( &m_ensembleCurveSet ); uiOrdering.add( &m_ensembleStatisticsType ); } caf::PdmUiGroup* regressionCurveGroup = uiOrdering.addNewGroup( "Regression Analysis" ); regressionCurveGroup->add( &m_regressionType ); if ( m_regressionType == RegressionType::POLYNOMIAL ) { regressionCurveGroup->add( &m_polynomialDegree ); } regressionCurveGroup->add( &m_expressionText ); caf::PdmUiGroup* valueRangeYGroup = uiOrdering.addNewGroup( "Value Range Y" ); valueRangeYGroup->add( &m_yRangeSelection ); if ( m_yRangeSelection() == RangeType::USER_DEFINED_RANGE ) { valueRangeYGroup->add( &m_valueRangeY ); } if ( axisTypeX() == RiaDefines::HorizontalAxisType::SUMMARY_VECTOR ) { caf::PdmUiGroup* valueRangeXGroup = uiOrdering.addNewGroup( "Value Range X" ); valueRangeXGroup->add( &m_xRangeSelection ); if ( m_xRangeSelection() == RangeType::USER_DEFINED_RANGE ) { valueRangeXGroup->add( &m_valueRangeX ); } } else { caf::PdmUiGroup* timeSelectionGroup = uiOrdering.addNewGroup( "Time Selection" ); timeSelectionGroup->add( &m_timeRangeSelection ); if ( m_timeRangeSelection() == RangeType::USER_DEFINED_RANGE ) { timeSelectionGroup->add( &m_minTimeSliderPosition ); timeSelectionGroup->add( &m_maxTimeSliderPosition ); } timeSelectionGroup->add( &m_showTimeSelectionInPlot ); } caf::PdmUiGroup* forecastingGroup = uiOrdering.addNewGroup( "Forecasting" ); forecastingGroup->add( &m_forecastForward ); forecastingGroup->add( &m_forecastBackward ); forecastingGroup->add( &m_forecastUnit ); if ( m_dataSourceForRegression() == DataSource::ENSEMBLE ) { caf::PdmUiGroup* appearanceGroup = uiOrdering.addNewGroup( "Appearance" ); RimPlotCurve::appearanceUiOrdering( *appearanceGroup ); caf::PdmUiGroup* nameGroup = uiOrdering.addNewGroup( "Curve Name" ); nameGroup->setCollapsedByDefault(); nameGroup->add( &m_showLegend ); RimPlotCurve::curveNameUiOrdering( *nameGroup ); } else { RimSummaryCurve::defineUiOrdering( uiConfigName, uiOrdering ); } uiOrdering.skipRemainingFields(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { RimSummaryCurve::fieldChangedByUi( changedField, oldValue, newValue ); if ( &m_minTimeSliderPosition == changedField && m_minTimeSliderPosition > m_maxTimeSliderPosition ) { m_maxTimeSliderPosition = m_minTimeSliderPosition; } if ( &m_maxTimeSliderPosition == changedField && m_maxTimeSliderPosition < m_minTimeSliderPosition ) { m_minTimeSliderPosition = m_maxTimeSliderPosition; } loadAndUpdateDataAndPlot(); auto plot = firstAncestorOrThisOfTypeAsserted(); if ( plot ) plot->zoomAll(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) { RimSummaryCurve::defineEditorAttribute( field, uiConfigName, attribute ); if ( field == &m_polynomialDegree ) { if ( auto* lineEditorAttr = dynamic_cast( attribute ) ) { // Polynomial degree should be a positive number. lineEditorAttr->validator = new QIntValidator( 1, 50, nullptr ); } } else if ( field == &m_forecastForward || field == &m_forecastBackward ) { if ( auto* lineEditorAttr = dynamic_cast( attribute ) ) { // Block negative forecast lineEditorAttr->validator = new QIntValidator( 0, 50, nullptr ); } } else if ( field == &m_minTimeSliderPosition || field == &m_maxTimeSliderPosition ) { if ( auto* myAttr = dynamic_cast( attribute ) ) { myAttr->m_minimum = 0; myAttr->m_maximum = 100; myAttr->m_showSpinBox = false; } } else if ( field == &m_expressionText ) { auto myAttr = dynamic_cast( attribute ); if ( myAttr ) { myAttr->wrapMode = caf::PdmUiTextEditorAttribute::NoWrap; myAttr->textMode = caf::PdmUiTextEditorAttribute::HTML; QFont font; auto pointSize = font.pointSize(); font.setPointSize( pointSize + 2 ); myAttr->font = font; } } else if ( field == &m_valueRangeX ) { if ( auto attr = dynamic_cast( attribute ) ) { attr->m_decimals = 2; attr->m_sliderTickCount = 100; auto values = m_sourceValuesX; if ( !values.empty() ) { attr->m_minimum = *std::min_element( values.begin(), values.end() ); attr->m_maximum = *std::max_element( values.begin(), values.end() ); } } } else if ( field == &m_valueRangeY ) { if ( auto attr = dynamic_cast( attribute ) ) { attr->m_decimals = 2; attr->m_sliderTickCount = 100; auto values = m_sourceValuesY; if ( !values.empty() ) { attr->m_minimum = *std::min_element( values.begin(), values.end() ); attr->m_maximum = *std::max_element( values.begin(), values.end() ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimSummaryRegressionAnalysisCurve::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { if ( fieldNeedingOptions == &m_ensembleCurveSet ) { QList options; options.append( { "None", nullptr } ); auto plot = firstAncestorOrThisOfType(); if ( plot ) { auto curveSets = plot->ensembleCurveSetCollection()->curveSets(); for ( auto curveSet : curveSets ) { options.append( { curveSet->name(), curveSet } ); } } return options; } if ( fieldNeedingOptions == &m_ensembleStatisticsType ) { QList options; options.append( { "None", nullptr } ); if ( m_ensembleCurveSet() ) { auto statisticsOptions = m_ensembleCurveSet()->statisticsOptions(); std::vector availableStatistics; if ( statisticsOptions->showP10Curve() ) { availableStatistics.push_back( RifEclipseSummaryAddressDefines::StatisticsType::P10 ); } if ( statisticsOptions->showP50Curve() ) { availableStatistics.push_back( RifEclipseSummaryAddressDefines::StatisticsType::P50 ); } if ( statisticsOptions->showP90Curve() ) { availableStatistics.push_back( RifEclipseSummaryAddressDefines::StatisticsType::P90 ); } if ( statisticsOptions->showMeanCurve() ) { availableStatistics.push_back( RifEclipseSummaryAddressDefines::StatisticsType::MEAN ); } for ( const auto& statisticsType : availableStatistics ) { options.push_back( caf::PdmOptionItemInfo( caf::AppEnum::uiText( statisticsType ), statisticsType ) ); } } return options; } return RimSummaryCurve::calculateValueOptions( fieldNeedingOptions ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::createCurveAutoName() { QString sourceCurveName; if ( m_dataSourceForRegression() == DataSource::ENSEMBLE ) { if ( m_ensembleCurveSet() ) { sourceCurveName = m_ensembleCurveSet()->name(); } } else { sourceCurveName = RimSummaryCurve::createCurveAutoName(); } return sourceCurveName + " " + m_regressionType().uiText() + " Regression"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::curveExportDescription( const RifEclipseSummaryAddress& address ) const { return RimSummaryCurve::curveExportDescription( {} ) + "." + m_regressionType().uiText() + "_Regression"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::LinearRegression& reg ) { return RiaRegressionTextTools::generateRegressionText( reg ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::PolynomialRegression& reg ) { return RiaRegressionTextTools::generateRegressionText( reg ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::PowerFitRegression& reg ) { return RiaRegressionTextTools::generateRegressionText( reg ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::ExponentialRegression& reg ) { return RiaRegressionTextTools::generateRegressionText( reg ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryRegressionAnalysisCurve::generateRegressionText( const regression::LogarithmicRegression& reg ) { return RiaRegressionTextTools::generateRegressionText( reg ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::appendTimeSteps( std::vector& destinationTimeSteps, const std::set& sourceTimeSteps ) { for ( const QDateTime& t : sourceTimeSteps ) destinationTimeSteps.push_back( RiaTimeTTools::fromQDateTime( t ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RimSummaryRegressionAnalysisCurve::fullTimeStepRange() const { auto timeSteps = m_sourceTimeStepsY; if ( !timeSteps.empty() ) { return std::make_pair( *timeSteps.begin(), *timeSteps.rbegin() ); } return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RimSummaryRegressionAnalysisCurve::selectedTimeStepRange() const { // Scale the slider values to the full time step range auto [min, max] = fullTimeStepRange(); auto range = max - min; auto selectedMin = min + static_cast( range * ( m_minTimeSliderPosition / 100.0 ) ); auto selectedMax = min + static_cast( range * ( m_maxTimeSliderPosition / 100.0 ) ); return { selectedMin, selectedMax }; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::getOutputTimeSteps( const std::vector& timeSteps, int forecastBackward, int forecastForward, ForecastUnit forecastUnit ) { auto getTimeSpan = []( int value, ForecastUnit unit ) { if ( unit == ForecastUnit::YEARS ) return DateTimeSpan( value, 0, 0 ); if ( unit == ForecastUnit::MONTHS ) return DateTimeSpan( 0, value, 0 ); CAF_ASSERT( unit == ForecastUnit::DAYS ); return DateTimeSpan( 0, 0, value ); }; int numDates = 50; std::vector outputTimeSteps; if ( forecastBackward > 0 ) { QDateTime firstTimeStepInData = RiaQDateTimeTools::fromTime_t( timeSteps.front() ); QDateTime forecastStartTimeStep = RiaQDateTimeTools::subtractSpan( firstTimeStepInData, getTimeSpan( forecastBackward, forecastUnit ) ); auto forecastTimeSteps = RiaQDateTimeTools::createEvenlyDistributedDatesInInterval( forecastStartTimeStep, firstTimeStepInData, numDates ); appendTimeSteps( outputTimeSteps, forecastTimeSteps ); } outputTimeSteps.insert( std::end( outputTimeSteps ), std::begin( timeSteps ), std::end( timeSteps ) ); if ( forecastForward > 0 ) { QDateTime lastTimeStepInData = RiaQDateTimeTools::fromTime_t( timeSteps.back() ); QDateTime forecastEndTimeStep = RiaQDateTimeTools::addSpan( lastTimeStepInData, getTimeSpan( forecastForward, forecastUnit ) ); auto forecastTimeSteps = RiaQDateTimeTools::createEvenlyDistributedDatesInInterval( lastTimeStepInData, forecastEndTimeStep, numDates ); appendTimeSteps( outputTimeSteps, forecastTimeSteps ); } return outputTimeSteps; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimSummaryRegressionAnalysisCurve::isRegressionCurve() const { return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::convertToDouble( const std::vector& timeSteps ) { std::vector doubleVector( timeSteps.size() ); std::transform( timeSteps.begin(), timeSteps.end(), doubleVector.begin(), []( const auto& timeVal ) { return static_cast( timeVal ); } ); return doubleVector; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimSummaryRegressionAnalysisCurve::convertToTimeT( const std::vector& timeSteps ) { std::vector tVector( timeSteps.size() ); std::transform( timeSteps.begin(), timeSteps.end(), tVector.begin(), []( const auto& timeVal ) { return static_cast( timeVal ); } ); return tVector; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair, std::vector> RimSummaryRegressionAnalysisCurve::getPositiveValues( const std::vector& timeSteps, const std::vector& values ) { std::vector filteredTimeSteps; std::vector filteredValues; for ( size_t i = 0; i < timeSteps.size(); i++ ) { if ( timeSteps[i] > 0.0 && values[i] > 0.0 ) { filteredTimeSteps.push_back( timeSteps[i] ); filteredValues.push_back( values[i] ); } } return std::make_pair( filteredTimeSteps, filteredValues ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair, std::vector> RimSummaryRegressionAnalysisCurve::getInRangeValues( const std::vector& timeSteps, const std::vector& values, time_t minTimeStep, time_t maxTimeStep ) { CAF_ASSERT( timeSteps.size() == values.size() ); std::vector filteredTimeSteps; std::vector filteredValues; for ( size_t i = 0; i < timeSteps.size(); i++ ) { time_t timeStep = timeSteps[i]; if ( timeStep >= minTimeStep && timeStep <= maxTimeStep ) { filteredTimeSteps.push_back( timeStep ); filteredValues.push_back( values[i] ); } } return std::make_pair( filteredTimeSteps, filteredValues ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::updateTimeAnnotations() { auto plot = firstAncestorOrThisOfTypeAsserted(); if ( m_timeRangeAnnotation ) plot->removeTimeAnnotation( m_timeRangeAnnotation ); if ( m_showTimeSelectionInPlot && isChecked() ) { auto [minTimeStep, maxTimeStep] = selectedTimeStepRange(); m_timeRangeAnnotation = plot->addTimeRangeAnnotation( minTimeStep, maxTimeStep ); m_timeRangeAnnotation->setColor( color() ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryRegressionAnalysisCurve::updateDefaultValues() { if ( !m_sourceTimeStepsY.empty() && m_timeRangeSelection() == RangeType::FULL_RANGE ) { m_minTimeSliderPosition = 0; m_maxTimeSliderPosition = 100; } if ( !m_sourceValuesX.empty() && m_xRangeSelection() == RangeType::FULL_RANGE ) { m_valueRangeX = std::make_pair( *std::min_element( m_sourceValuesX.begin(), m_sourceValuesX.end() ), *std::max_element( m_sourceValuesX.begin(), m_sourceValuesX.end() ) ); } if ( !m_sourceValuesY.empty() && m_yRangeSelection() == RangeType::FULL_RANGE ) { m_valueRangeY = std::make_pair( *std::min_element( m_sourceValuesY.begin(), m_sourceValuesY.end() ), *std::max_element( m_sourceValuesY.begin(), m_sourceValuesY.end() ) ); } }