diff --git a/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.cpp b/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.cpp index ca85154d13..344a52ac20 100644 --- a/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.cpp +++ b/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.cpp @@ -603,3 +603,25 @@ std::vector RiaQDateTimeTools::getTimeStepsWithinSelectedRange( const return selectedTimeSteps; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::set + RiaQDateTimeTools::createEvenlyDistributedDatesInInterval( const QDateTime& fromTimeStamp, const QDateTime& toTimeStamp, int numDates ) +{ + if ( numDates < 2 ) return {}; + + // Calculate the time step between the two time stamps + qint64 timeStep = ( toTimeStamp.toMSecsSinceEpoch() - fromTimeStamp.toMSecsSinceEpoch() ) / ( static_cast( numDates ) - 1 ); + + // Create a set of evenly spaced datetimes. + std::set outputDates; + for ( int i = 0; i < numDates; ++i ) + { + qint64 targetTime = i * timeStep; + outputDates.insert( RiaQDateTimeTools::addMSecs( fromTimeStamp, targetTime ) ); + } + + return outputDates; +} diff --git a/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.h b/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.h index 79c5bddf07..334abbeb93 100644 --- a/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.h +++ b/ApplicationLibCode/Application/Tools/RiaQDateTimeTools.h @@ -95,6 +95,8 @@ public: static QList createOptionItems( const std::vector& timeSteps ); static std::set createEvenlyDistributedDates( const std::vector& inputDates, int numDates ); + static std::set + createEvenlyDistributedDatesInInterval( const QDateTime& fromTimeStep, const QDateTime& toTimeStep, int numDates ); static std::vector getTimeStepsWithinSelectedRange( const std::vector& timeSteps, const QDateTime& fromTimeStep, const QDateTime& toTimeStep ); diff --git a/ApplicationLibCode/Commands/SummaryPlotCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/SummaryPlotCommands/CMakeLists_files.cmake index 706b8a9f55..2763e37eb4 100644 --- a/ApplicationLibCode/Commands/SummaryPlotCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/SummaryPlotCommands/CMakeLists_files.cmake @@ -51,6 +51,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicOpenSummaryPlotEditorFromMdiAreaFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewSummaryTableFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicDuplicateSummaryTableFeature.h + ${CMAKE_CURRENT_LIST_DIR}/RicCreateDeclineCurvesFeature.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -106,6 +107,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicOpenSummaryPlotEditorFromMdiAreaFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewSummaryTableFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicDuplicateSummaryTableFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicCreateDeclineCurvesFeature.cpp ) list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/Commands/SummaryPlotCommands/RicCreateDeclineCurvesFeature.cpp b/ApplicationLibCode/Commands/SummaryPlotCommands/RicCreateDeclineCurvesFeature.cpp new file mode 100644 index 0000000000..bcb040a65e --- /dev/null +++ b/ApplicationLibCode/Commands/SummaryPlotCommands/RicCreateDeclineCurvesFeature.cpp @@ -0,0 +1,125 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RicCreateDeclineCurvesFeature.h" + +#include "RiaSummaryTools.h" + +#include "RimSummaryCurve.h" +#include "RimSummaryDeclineCurve.h" +#include "RimSummaryMultiPlot.h" +#include "RimSummaryPlot.h" +#include "RiuPlotMainWindowTools.h" + +#include "cafSelectionManagerTools.h" + +#include "cvfAssert.h" + +#include + +CAF_CMD_SOURCE_INIT( RicCreateDeclineCurvesFeature, "RicCreateDeclineCurvesFeature" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicCreateDeclineCurvesFeature::isCommandEnabled() +{ + RimSummaryPlot* selectedPlot = caf::firstAncestorOfTypeFromSelectedObject(); + return ( selectedPlot && !RiaSummaryTools::isSummaryCrossPlot( selectedPlot ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCreateDeclineCurvesFeature::onActionTriggered( bool isChecked ) +{ + RimSummaryCurve* curve = caf::firstAncestorOfTypeFromSelectedObject(); + if ( curve ) + { + std::vector declineCurveTypes = { RimSummaryDeclineCurve::DeclineCurveType::EXPONENTIAL, + RimSummaryDeclineCurve::DeclineCurveType::HYPERBOLIC, + RimSummaryDeclineCurve::DeclineCurveType::HARMONIC }; + + for ( auto declineCurveType : declineCurveTypes ) + { + RimSummaryDeclineCurve* newCurve = createDeclineCurveAndAddToPlot( curve, declineCurveType ); + + RiuPlotMainWindowTools::showPlotMainWindow(); + RiuPlotMainWindowTools::selectAsCurrentItem( newCurve ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCreateDeclineCurvesFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setText( "Create Decline Curves" ); + actionToSetup->setIcon( QIcon( ":/SummaryCurve16x16.png" ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSummaryDeclineCurve* RicCreateDeclineCurvesFeature::createDeclineCurveAndAddToPlot( RimSummaryCurve* sourceCurve, + RimSummaryDeclineCurve::DeclineCurveType declineCurveType ) +{ + auto mapToLineStyle = []( RimSummaryDeclineCurve::DeclineCurveType t ) + { + if ( t == RimSummaryDeclineCurve::DeclineCurveType::HARMONIC ) return RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_DOT; + if ( t == RimSummaryDeclineCurve::DeclineCurveType::HYPERBOLIC ) return RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_DASH; + return RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_DASH_DOT; + }; + + RimSummaryPlot* summaryPlot = caf::firstAncestorOfTypeFromSelectedObject(); + + RimSummaryDeclineCurve* newCurve = new RimSummaryDeclineCurve(); + CVF_ASSERT( newCurve ); + + newCurve->setSummaryCaseX( sourceCurve->summaryCaseX() ); + newCurve->setSummaryAddressX( sourceCurve->summaryAddressX() ); + + newCurve->setSummaryCaseY( sourceCurve->summaryCaseY() ); + newCurve->setSummaryAddressY( sourceCurve->summaryAddressY() ); + + newCurve->setDeclineCurveType( declineCurveType ); + + newCurve->setColor( sourceCurve->color() ); + newCurve->setLineStyle( mapToLineStyle( declineCurveType ) ); + + summaryPlot->addCurveAndUpdate( newCurve ); + + newCurve->loadDataAndUpdate( true ); + newCurve->updateConnectedEditors(); + + RimSummaryMultiPlot* summaryMultiPlot = nullptr; + summaryPlot->firstAncestorOrThisOfType( summaryMultiPlot ); + if ( summaryMultiPlot ) + { + summaryMultiPlot->updatePlotTitles(); + } + else + { + summaryPlot->updatePlotTitle(); + } + + summaryPlot->updateAllRequiredEditors(); + + return newCurve; +} diff --git a/ApplicationLibCode/Commands/SummaryPlotCommands/RicCreateDeclineCurvesFeature.h b/ApplicationLibCode/Commands/SummaryPlotCommands/RicCreateDeclineCurvesFeature.h new file mode 100644 index 0000000000..129a960235 --- /dev/null +++ b/ApplicationLibCode/Commands/SummaryPlotCommands/RicCreateDeclineCurvesFeature.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafCmdFeature.h" + +#include "RimSummaryDeclineCurve.h" + +class RimSummaryPlot; +class RimSummaryCurve; + +//================================================================================================== +/// +//================================================================================================== +class RicCreateDeclineCurvesFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +protected: + bool isCommandEnabled() override; + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; + + static RimSummaryDeclineCurve* createDeclineCurveAndAddToPlot( RimSummaryCurve* sourceCurve, + RimSummaryDeclineCurve::DeclineCurveType declineCurveType ); +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp index 65370cfdba..8ee4576dc6 100644 --- a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -717,6 +717,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() menuBuilder << "Separator"; menuBuilder << "RicNewSummaryCurveFeature"; menuBuilder << "RicDuplicateSummaryCurveFeature"; + menuBuilder << "RicCreateDeclineCurvesFeature"; menuBuilder << "RicNewSummaryCrossPlotCurveFeature"; menuBuilder << "RicDuplicateSummaryCrossPlotCurveFeature"; menuBuilder << "Separator"; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake index 7ca8a29fcf..1f4dff506b 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake @@ -50,6 +50,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.h ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.h + ${CMAKE_CURRENT_LIST_DIR}/RimSummaryDeclineCurve.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -104,6 +105,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimSummaryDeclineCurve.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp index fde37d764d..3f7c3d83fd 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp @@ -1233,19 +1233,31 @@ void RimSummaryCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField, if ( loadAndUpdate ) { - this->loadDataAndUpdate( true ); - - plot->updateAxes(); - plot->updatePlotTitle(); - plot->updateConnectedEditors(); - - RiuPlotMainWindow* mainPlotWindow = RiaGuiApplication::instance()->mainPlotWindow(); - mainPlotWindow->updateMultiPlotToolBar(); - - dataChanged.send(); + loadAndUpdateDataAndPlot(); } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryCurve::loadAndUpdateDataAndPlot() +{ + this->loadDataAndUpdate( true ); + + RimSummaryPlot* plot = nullptr; + firstAncestorOrThisOfType( plot ); + CVF_ASSERT( plot ); + + plot->updateAxes(); + plot->updatePlotTitle(); + plot->updateConnectedEditors(); + + RiuPlotMainWindow* mainPlotWindow = RiaGuiApplication::instance()->mainPlotWindow(); + mainPlotWindow->updateMultiPlotToolBar(); + + dataChanged.send(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h index 940675985f..8e670eb100 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h @@ -53,11 +53,11 @@ public: ~RimSummaryCurve() override; // Y Axis functions - RiaSummaryCurveDefinition curveDefinitionY() const; - RimSummaryCase* summaryCaseY() const; - RifEclipseSummaryAddress summaryAddressY() const; - std::string unitNameY() const; - std::vector valuesY() const; + RiaSummaryCurveDefinition curveDefinitionY() const; + RimSummaryCase* summaryCaseY() const; + RifEclipseSummaryAddress summaryAddressY() const; + std::string unitNameY() const; + virtual std::vector valuesY() const; void applyCurveDefinitionY( const RiaSummaryCurveDefinition& curveDefinition ); void setSummaryCaseY( RimSummaryCase* sumCase ); @@ -65,20 +65,20 @@ public: void setSummaryAddressY( const RifEclipseSummaryAddress& address ); void setResampling( RiaDefines::DateTimePeriodEnum resampling ); - RifEclipseSummaryAddress errorSummaryAddressY() const; - std::vector errorValuesY() const; - void setLeftOrRightAxisY( RiuPlotAxis plotAxis ); - RiuPlotAxis axisY() const; - std::vector timeStepsY() const; - double yValueAtTimeT( time_t time ) const; - void setOverrideCurveDataY( const std::vector& xValues, const std::vector& yValues ); + RifEclipseSummaryAddress errorSummaryAddressY() const; + std::vector errorValuesY() const; + void setLeftOrRightAxisY( RiuPlotAxis plotAxis ); + RiuPlotAxis axisY() const; + virtual std::vector timeStepsY() const; + double yValueAtTimeT( time_t time ) const; + void setOverrideCurveDataY( const std::vector& xValues, const std::vector& yValues ); // X Axis functions - RiaSummaryCurveDefinition curveDefinitionX() const; - RimSummaryCase* summaryCaseX() const; - RifEclipseSummaryAddress summaryAddressX() const; - std::string unitNameX() const; - std::vector valuesX() const; + RiaSummaryCurveDefinition curveDefinitionX() const; + RimSummaryCase* summaryCaseX() const; + RifEclipseSummaryAddress summaryAddressX() const; + std::string unitNameX() const; + virtual std::vector valuesX() const; void setSummaryCaseX( RimSummaryCase* sumCase ); void setSummaryAddressX( const RifEclipseSummaryAddress& address ); @@ -105,18 +105,15 @@ protected: void updateZoomInParentPlot() override; void onLoadDataAndUpdate( bool updateParentPlot ) override; + void loadAndUpdateDataAndPlot(); + void updateLegendsInPlot() override; void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; void initAfterRead() override; double computeCurveZValue() override; -private: - RifSummaryReaderInterface* valuesSummaryReaderX() const; - RifSummaryReaderInterface* valuesSummaryReaderY() const; - std::vector timeStepsX() const; - - void calculateCurveInterpolationFromAddress(); + virtual std::vector timeStepsX() const; // Overridden PDM methods void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; @@ -124,6 +121,12 @@ private: void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; +private: + RifSummaryReaderInterface* valuesSummaryReaderX() const; + RifSummaryReaderInterface* valuesSummaryReaderY() const; + + void calculateCurveInterpolationFromAddress(); + static void appendOptionItemsForSummaryAddresses( QList* options, RimSummaryCase* summaryCase ); private: diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryDeclineCurve.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryDeclineCurve.cpp new file mode 100644 index 0000000000..f079d64ed7 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryDeclineCurve.cpp @@ -0,0 +1,343 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RimSummaryDeclineCurve.h" + +#include "RiaQDateTimeTools.h" +#include "RiaSummaryTools.h" +#include "RiaTimeTTools.h" + +#include "RigDeclineCurveCalculator.h" + +#include "cafPdmUiDoubleSliderEditor.h" +#include "cafPdmUiLineEditor.h" + +#include + +#include + +CAF_PDM_SOURCE_INIT( RimSummaryDeclineCurve, "DeclineCurve" ); + +namespace caf +{ +template <> +void caf::AppEnum::setUp() +{ + addItem( RimSummaryDeclineCurve::DeclineCurveType::EXPONENTIAL, "EXPONENTIAL", "Exponential" ); + addItem( RimSummaryDeclineCurve::DeclineCurveType::HARMONIC, "HARMONIC", "Harmonic" ); + addItem( RimSummaryDeclineCurve::DeclineCurveType::HYPERBOLIC, "HYPERBOLIC", "Hyperbolic" ); + setDefault( RimSummaryDeclineCurve::DeclineCurveType::HARMONIC ); +} +}; // namespace caf + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSummaryDeclineCurve::RimSummaryDeclineCurve() +{ + CAF_PDM_InitObject( "Decline Curve", ":/SummaryCurve16x16.png" ); + + CAF_PDM_InitFieldNoDefault( &m_declineCurveType, "DeclineCurveType", "Type" ); + CAF_PDM_InitField( &m_predictionYears, "PredictionYears", 5, "Years" ); + CAF_PDM_InitField( &m_hyperbolicDeclineConstant, "HyperbolicDeclineConstant", 0.5, "Decline Constant" ); + m_hyperbolicDeclineConstant.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSummaryDeclineCurve::~RimSummaryDeclineCurve() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimSummaryDeclineCurve::valuesY() const +{ + return createDeclineCurveValues( RimSummaryCurve::valuesY(), + RimSummaryCurve::timeStepsY(), + RiaSummaryTools::hasAccumulatedData( summaryAddressY() ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimSummaryDeclineCurve::valuesX() const +{ + return createDeclineCurveValues( RimSummaryCurve::valuesX(), + RimSummaryCurve::timeStepsX(), + RiaSummaryTools::hasAccumulatedData( summaryAddressX() ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimSummaryDeclineCurve::timeStepsY() const +{ + std::vector timeSteps = RimSummaryCurve::timeStepsY(); + appendFutureTimeSteps( timeSteps ); + return timeSteps; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimSummaryDeclineCurve::timeStepsX() const +{ + std::vector timeSteps = RimSummaryCurve::timeStepsX(); + appendFutureTimeSteps( timeSteps ); + return timeSteps; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimSummaryDeclineCurve::createDeclineCurveValues( const std::vector& values, + const std::vector& timeSteps, + bool isAccumulatedResult ) const +{ + if ( values.empty() ) return values; + if ( timeSteps.empty() ) return values; + + auto [initialProductionRate, initialDeclineRate] = computeInitialProductionAndDeclineRate( values, timeSteps, isAccumulatedResult ); + if ( std::isinf( initialProductionRate ) || std::isnan( initialProductionRate ) || std::isinf( initialDeclineRate ) || + std::isnan( initialDeclineRate ) ) + { + return values; + } + + QDateTime initialTime = RiaQDateTimeTools::fromTime_t( timeSteps.back() ); + + std::set futureTimeSteps = createFutureTimeSteps( timeSteps ); + std::vector outValues = values; + for ( const QDateTime& futureTime : futureTimeSteps ) + { + double timeSinceStart = futureTime.toSecsSinceEpoch() - initialTime.toSecsSinceEpoch(); + double predictedValue = computePredictedValue( initialProductionRate, initialDeclineRate, timeSinceStart, isAccumulatedResult ); + if ( isAccumulatedResult ) predictedValue += values.back(); + outValues.push_back( predictedValue ); + } + + return outValues; +} + +std::pair RimSummaryDeclineCurve::computeInitialProductionAndDeclineRate( const std::vector& values, + const std::vector& timeSteps, + bool isAccumulatedResult ) +{ + auto computeProductionRate = []( double t0, double v0, double t1, double v1 ) { return ( v1 - v0 ) / ( t1 - t0 ); }; + + const double historyStep = 0.25; + + // Select a point a 1/4 back in the existing curve. + const size_t idx0 = static_cast( timeSteps.size() * ( 1.0 - historyStep ) ); + const QDateTime t0 = RiaQDateTimeTools::fromTime_t( timeSteps[idx0] ); + const double v0 = values[idx0]; + + const QDateTime initialTime = RiaQDateTimeTools::fromTime_t( timeSteps.back() ); + + if ( !isAccumulatedResult ) + { + // Last point on the existing curve is the initial production rate (for non-accumulated data). + double initialProductionRate = values.back(); + + // Compute the decline rate using the rates at the two points + double initialDeclineRate = + RigDeclineCurveCalculator::computeDeclineRate( t0.toSecsSinceEpoch(), v0, initialTime.toSecsSinceEpoch(), initialProductionRate ); + return { initialProductionRate, initialDeclineRate }; + } + else + { + // For accumulated result: compute the initial production rate from the two points. + double initialProductionRate = computeProductionRate( t0.toSecsSinceEpoch(), v0, initialTime.toSecsSinceEpoch(), values.back() ); + + // Compute the at production rate at time t0 by using a point even further back in the existing curve. + size_t idxX = static_cast( timeSteps.size() * ( 1.0 - ( historyStep * 2 ) ) ); + QDateTime tx = RiaQDateTimeTools::fromTime_t( timeSteps[idxX] ); + double vx = values[idxX]; + double productionRate0 = computeProductionRate( tx.toSecsSinceEpoch(), vx, t0.toSecsSinceEpoch(), v0 ); + + // Compute the decline rate using the rates at the two points + double initialDeclineRate = RigDeclineCurveCalculator::computeDeclineRate( t0.toSecsSinceEpoch(), + productionRate0, + initialTime.toSecsSinceEpoch(), + initialProductionRate ); + return { initialProductionRate, initialDeclineRate }; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RimSummaryDeclineCurve::computePredictedValue( double initialProductionRate, + double initialDeclineRate, + double timeSinceStart, + bool isAccumulatedResult ) const +{ + if ( isAccumulatedResult ) + { + if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::EXPONENTIAL ) + { + return RigDeclineCurveCalculator::computeCumulativeProductionExponentialDecline( initialProductionRate, + initialDeclineRate, + timeSinceStart ); + } + else if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::HARMONIC ) + { + return RigDeclineCurveCalculator::computeCumulativeProductionHarmonicDecline( initialProductionRate, + initialDeclineRate, + timeSinceStart ); + } + else if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::HYPERBOLIC ) + { + return RigDeclineCurveCalculator::computeCumulativeProductionHyperbolicDecline( initialProductionRate, + initialDeclineRate, + timeSinceStart, + m_hyperbolicDeclineConstant ); + } + } + else + { + if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::EXPONENTIAL ) + { + return RigDeclineCurveCalculator::computeFlowRateExponentialDecline( initialProductionRate, initialDeclineRate, timeSinceStart ); + } + else if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::HARMONIC ) + { + return RigDeclineCurveCalculator::computeFlowRateHarmonicDecline( initialProductionRate, initialDeclineRate, timeSinceStart ); + } + else if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::HYPERBOLIC ) + { + return RigDeclineCurveCalculator::computeFlowRateHyperbolicDecline( initialProductionRate, + initialDeclineRate, + timeSinceStart, + m_hyperbolicDeclineConstant ); + } + } + + return 0.0; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::set RimSummaryDeclineCurve::createFutureTimeSteps( const std::vector& timeSteps ) const +{ + if ( timeSteps.empty() ) return {}; + + // Create additional time steps + QDateTime lastTimeStep = RiaQDateTimeTools::fromTime_t( timeSteps.back() ); + QDateTime predictionEnd = RiaQDateTimeTools::addYears( lastTimeStep, m_predictionYears() ); + + int numDates = 50; + return RiaQDateTimeTools::createEvenlyDistributedDatesInInterval( lastTimeStep, predictionEnd, numDates ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryDeclineCurve::appendFutureTimeSteps( std::vector& timeSteps ) const +{ + std::set futureTimeSteps = createFutureTimeSteps( timeSteps ); + appendTimeSteps( timeSteps, futureTimeSteps ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryDeclineCurve::appendTimeSteps( std::vector& timeSteps, const std::set& moreTimeSteps ) +{ + for ( const QDateTime& t : moreTimeSteps ) + timeSteps.push_back( RiaTimeTTools::fromQDateTime( t ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryDeclineCurve::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + RimPlotCurve::updateFieldUiState(); + + caf::PdmUiGroup* declineCurveGroup = uiOrdering.addNewGroup( "Decline Curve" ); + declineCurveGroup->add( &m_declineCurveType ); + declineCurveGroup->add( &m_predictionYears ); + + if ( m_declineCurveType == RimSummaryDeclineCurve::DeclineCurveType::HYPERBOLIC ) + { + declineCurveGroup->add( &m_hyperbolicDeclineConstant ); + } + + RimSummaryCurve::defineUiOrdering( uiConfigName, uiOrdering ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryDeclineCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) +{ + RimSummaryCurve::fieldChangedByUi( changedField, oldValue, newValue ); + if ( changedField == &m_declineCurveType || changedField == &m_predictionYears || changedField == &m_hyperbolicDeclineConstant ) + { + loadAndUpdateDataAndPlot(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryDeclineCurve::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) +{ + RimSummaryCurve::defineEditorAttribute( field, uiConfigName, attribute ); + + if ( field == &m_predictionYears ) + { + if ( auto* lineEditorAttr = dynamic_cast( attribute ) ) + { + // Predict into the future should be a positive number. + lineEditorAttr->validator = new QIntValidator( 1, 50, nullptr ); + } + } + + if ( field == &m_hyperbolicDeclineConstant ) + { + if ( auto* myAttr = dynamic_cast( attribute ) ) + { + // Hyperbolic decline constant must be larger than 0 to avoid calculation issues. + myAttr->m_minimum = 0.001; + myAttr->m_maximum = 1.0; + myAttr->m_decimals = 2; + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSummaryDeclineCurve::setDeclineCurveType( DeclineCurveType declineCurveType ) +{ + m_declineCurveType = declineCurveType; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimSummaryDeclineCurve::createCurveAutoName() +{ + return RimSummaryCurve::createCurveAutoName() + " " + m_declineCurveType().uiText() + " Decline"; +} diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryDeclineCurve.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryDeclineCurve.h new file mode 100644 index 0000000000..a71a2a1e42 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryDeclineCurve.h @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmField.h" +#include "cafPdmObject.h" + +#include "RimSummaryCurve.h" + +#include "cafAppEnum.h" + +#include + +//================================================================================================== +/// +/// +//================================================================================================== +class RimSummaryDeclineCurve : public RimSummaryCurve +{ + CAF_PDM_HEADER_INIT; + +public: + enum class DeclineCurveType + { + EXPONENTIAL, + HARMONIC, + HYPERBOLIC + }; + + RimSummaryDeclineCurve(); + ~RimSummaryDeclineCurve() override; + + void setDeclineCurveType( DeclineCurveType declineCurveType ); + + // Y Axis functions + std::vector valuesY() const override; + std::vector timeStepsY() const override; + + // X Axis functions + std::vector valuesX() const override; + +private: + QString createCurveAutoName() override; + + std::vector timeStepsX() const override; + + // Overridden PDM methods + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; + + void appendFutureTimeSteps( std::vector& timeSteps ) const; + + std::vector + createDeclineCurveValues( const std::vector& values, const std::vector& timeSteps, bool isAccumulatedResult ) const; + + std::set createFutureTimeSteps( const std::vector& timeSteps ) const; + static void appendTimeSteps( std::vector& timeSteps, const std::set& moreTimeSteps ); + + static std::pair computeInitialProductionAndDeclineRate( const std::vector& values, + const std::vector& timeSteps, + bool isAccumulatedResult ); + + double computePredictedValue( double initialProductionRate, double initialDeclineRate, double timeSinceStart, bool isAccumulatedResult ) const; + + caf::PdmField> m_declineCurveType; + caf::PdmField m_predictionYears; + caf::PdmField m_hyperbolicDeclineConstant; +}; diff --git a/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake index c9e54322b6..5454e285fe 100644 --- a/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ReservoirDataModel/CMakeLists_files.cmake @@ -181,6 +181,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RigWellAllocationOverTime.cpp ${CMAKE_CURRENT_LIST_DIR}/RigWellResultBranch.cpp ${CMAKE_CURRENT_LIST_DIR}/RigWellResultFrame.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigDeclineCurveCalculator.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/ReservoirDataModel/RigDeclineCurveCalculator.cpp b/ApplicationLibCode/ReservoirDataModel/RigDeclineCurveCalculator.cpp new file mode 100644 index 0000000000..2bbd7d789e --- /dev/null +++ b/ApplicationLibCode/ReservoirDataModel/RigDeclineCurveCalculator.cpp @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RigDeclineCurveCalculator.h" + +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeDeclineRate( double time0, double value0, double time1, double value1 ) +{ + return ( 1.0 / ( time1 - time0 ) ) * std::log( value0 / value1 ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeFlowRateExponentialDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart ) +{ + return initialProductionRateQi * std::exp( -initialDeclineRateDi * timeSinceStart ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeFlowRateHarmonicDecline( double initialProductionRateQi, double initialDeclineRateDi, double timeSinceStart ) +{ + return initialProductionRateQi / ( 1.0 + initialDeclineRateDi * timeSinceStart ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeFlowRateHyperbolicDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart, + double hyperbolicDeclineConstantB ) +{ + return initialProductionRateQi / + std::pow( 1.0 + hyperbolicDeclineConstantB * initialDeclineRateDi * timeSinceStart, 1.0 / hyperbolicDeclineConstantB ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeCumulativeProductionExponentialDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart ) +{ + double productionRate = computeFlowRateExponentialDecline( initialProductionRateQi, initialDeclineRateDi, timeSinceStart ); + return ( 1 / initialDeclineRateDi ) * ( initialProductionRateQi - productionRate ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeCumulativeProductionHarmonicDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart ) +{ + double productionRate = computeFlowRateHarmonicDecline( initialProductionRateQi, initialDeclineRateDi, timeSinceStart ); + return ( initialProductionRateQi / initialDeclineRateDi ) * std::log( initialProductionRateQi / productionRate ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigDeclineCurveCalculator::computeCumulativeProductionHyperbolicDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart, + double hyperbolicDeclineConstantB ) +{ + double productionRate = + computeFlowRateHyperbolicDecline( initialProductionRateQi, initialDeclineRateDi, timeSinceStart, hyperbolicDeclineConstantB ); + + return ( std::pow( initialProductionRateQi, hyperbolicDeclineConstantB ) / ( ( 1.0 - hyperbolicDeclineConstantB ) * initialDeclineRateDi ) ) * + ( std::pow( initialProductionRateQi, 1.0 - hyperbolicDeclineConstantB ) - + std::pow( productionRate, 1.0 - hyperbolicDeclineConstantB ) ); +} diff --git a/ApplicationLibCode/ReservoirDataModel/RigDeclineCurveCalculator.h b/ApplicationLibCode/ReservoirDataModel/RigDeclineCurveCalculator.h new file mode 100644 index 0000000000..4a035fbf55 --- /dev/null +++ b/ApplicationLibCode/ReservoirDataModel/RigDeclineCurveCalculator.h @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//================================================================================================== +// +// +// +//================================================================================================== +class RigDeclineCurveCalculator +{ +public: + static double computeDeclineRate( double time0, double value0, double time1, double value1 ); + + static double computeFlowRateExponentialDecline( double initialProductionRateQi, double initialDeclineRateDi, double timeSinceStart ); + + static double computeFlowRateHarmonicDecline( double initialProductionRateQi, double initialDeclineRateDi, double timeSinceStart ); + + static double computeFlowRateHyperbolicDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart, + double hyperbolicDeclineConstantB ); + + static double + computeCumulativeProductionExponentialDecline( double initialProductionRateQi, double initialDeclineRateDi, double timeSinceStart ); + + static double + computeCumulativeProductionHarmonicDecline( double initialProductionRateQi, double initialDeclineRateDi, double timeSinceStart ); + + static double computeCumulativeProductionHyperbolicDecline( double initialProductionRateQi, + double initialDeclineRateDi, + double timeSinceStart, + double hyperbolicDeclineConstantB ); +}; diff --git a/ApplicationLibCode/UnitTests/CMakeLists_files.cmake b/ApplicationLibCode/UnitTests/CMakeLists_files.cmake index 1f44f2084c..d4611833ba 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists_files.cmake +++ b/ApplicationLibCode/UnitTests/CMakeLists_files.cmake @@ -90,6 +90,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RifRevealCsvSummaryReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifStimPlanCsvSummaryReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaEnsembleNameTools-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RigDeclineCurveCalculator-Test.cpp ) if(RESINSIGHT_ENABLE_GRPC) diff --git a/ApplicationLibCode/UnitTests/RigDeclineCurveCalculator-Test.cpp b/ApplicationLibCode/UnitTests/RigDeclineCurveCalculator-Test.cpp new file mode 100644 index 0000000000..d39b2531d7 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RigDeclineCurveCalculator-Test.cpp @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "gtest/gtest.h" + +#include "RigDeclineCurveCalculator.h" + +// Test case from https://web.archive.org/web/20120710104333/http://infohost.nmt.edu/~petro/faculty/Kelly/Deline.pdf +// Example Problem 8-1: +// "Given that a well has declined from 100 stb/day to 96 stb/day during a one-month period, +// use the exponential decline model to perform the following tasks: +// Predict the production rate after 11 more months." + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RigDeclineCurveCalculatorTests, computeDeclineRateExample ) +{ + double t0 = 0.0; + double q0 = 100.0; + double t1 = 1.0; + double q1 = 96.0; + double expectedDeclineRateDi = 0.04082; + + double declineRate = RigDeclineCurveCalculator::computeDeclineRate( t0, q0, t1, q1 ); + + EXPECT_NEAR( expectedDeclineRateDi, declineRate, 0.005 ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RigDeclineCurveCalculatorTests, exponentialFlowRateExample ) +{ + double initialProductionRateQi = 96.0; + double initialDeclineRateDi = 0.04082; + double timeSinceStart = 11.0; + + double q = RigDeclineCurveCalculator::computeFlowRateExponentialDecline( initialProductionRateQi, initialDeclineRateDi, timeSinceStart ); + + EXPECT_NEAR( q, 61.27, 0.005 ); +}