mirror of
https://github.com/OPM/ResInsight.git
synced 2025-02-25 18:55:39 -06:00
Add "Decline Curves" functionality.
Adapted from formulas here: https://petrowiki.spe.org/Production_forecasting_decline_curve_analysis
This commit is contained in:
@@ -603,3 +603,25 @@ std::vector<QDateTime> RiaQDateTimeTools::getTimeStepsWithinSelectedRange( const
|
|||||||
|
|
||||||
return selectedTimeSteps;
|
return selectedTimeSteps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::set<QDateTime>
|
||||||
|
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<qint64>( numDates ) - 1 );
|
||||||
|
|
||||||
|
// Create a set of evenly spaced datetimes.
|
||||||
|
std::set<QDateTime> outputDates;
|
||||||
|
for ( int i = 0; i < numDates; ++i )
|
||||||
|
{
|
||||||
|
qint64 targetTime = i * timeStep;
|
||||||
|
outputDates.insert( RiaQDateTimeTools::addMSecs( fromTimeStamp, targetTime ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputDates;
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ public:
|
|||||||
static QList<caf::PdmOptionItemInfo> createOptionItems( const std::vector<time_t>& timeSteps );
|
static QList<caf::PdmOptionItemInfo> createOptionItems( const std::vector<time_t>& timeSteps );
|
||||||
|
|
||||||
static std::set<QDateTime> createEvenlyDistributedDates( const std::vector<QDateTime>& inputDates, int numDates );
|
static std::set<QDateTime> createEvenlyDistributedDates( const std::vector<QDateTime>& inputDates, int numDates );
|
||||||
|
static std::set<QDateTime>
|
||||||
|
createEvenlyDistributedDatesInInterval( const QDateTime& fromTimeStep, const QDateTime& toTimeStep, int numDates );
|
||||||
static std::vector<QDateTime>
|
static std::vector<QDateTime>
|
||||||
getTimeStepsWithinSelectedRange( const std::vector<QDateTime>& timeSteps, const QDateTime& fromTimeStep, const QDateTime& toTimeStep );
|
getTimeStepsWithinSelectedRange( const std::vector<QDateTime>& timeSteps, const QDateTime& fromTimeStep, const QDateTime& toTimeStep );
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RicOpenSummaryPlotEditorFromMdiAreaFeature.h
|
${CMAKE_CURRENT_LIST_DIR}/RicOpenSummaryPlotEditorFromMdiAreaFeature.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicNewSummaryTableFeature.h
|
${CMAKE_CURRENT_LIST_DIR}/RicNewSummaryTableFeature.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicDuplicateSummaryTableFeature.h
|
${CMAKE_CURRENT_LIST_DIR}/RicDuplicateSummaryTableFeature.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateDeclineCurvesFeature.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCE_GROUP_SOURCE_FILES
|
set(SOURCE_GROUP_SOURCE_FILES
|
||||||
@@ -106,6 +107,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RicOpenSummaryPlotEditorFromMdiAreaFeature.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RicOpenSummaryPlotEditorFromMdiAreaFeature.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicNewSummaryTableFeature.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RicNewSummaryTableFeature.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RicDuplicateSummaryTableFeature.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RicDuplicateSummaryTableFeature.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RicCreateDeclineCurvesFeature.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// 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 <QAction>
|
||||||
|
|
||||||
|
CAF_CMD_SOURCE_INIT( RicCreateDeclineCurvesFeature, "RicCreateDeclineCurvesFeature" );
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bool RicCreateDeclineCurvesFeature::isCommandEnabled()
|
||||||
|
{
|
||||||
|
RimSummaryPlot* selectedPlot = caf::firstAncestorOfTypeFromSelectedObject<RimSummaryPlot*>();
|
||||||
|
return ( selectedPlot && !RiaSummaryTools::isSummaryCrossPlot( selectedPlot ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RicCreateDeclineCurvesFeature::onActionTriggered( bool isChecked )
|
||||||
|
{
|
||||||
|
RimSummaryCurve* curve = caf::firstAncestorOfTypeFromSelectedObject<RimSummaryCurve*>();
|
||||||
|
if ( curve )
|
||||||
|
{
|
||||||
|
std::vector<RimSummaryDeclineCurve::DeclineCurveType> 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<RimSummaryPlot*>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// 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 );
|
||||||
|
};
|
||||||
@@ -717,6 +717,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
|
|||||||
menuBuilder << "Separator";
|
menuBuilder << "Separator";
|
||||||
menuBuilder << "RicNewSummaryCurveFeature";
|
menuBuilder << "RicNewSummaryCurveFeature";
|
||||||
menuBuilder << "RicDuplicateSummaryCurveFeature";
|
menuBuilder << "RicDuplicateSummaryCurveFeature";
|
||||||
|
menuBuilder << "RicCreateDeclineCurvesFeature";
|
||||||
menuBuilder << "RicNewSummaryCrossPlotCurveFeature";
|
menuBuilder << "RicNewSummaryCrossPlotCurveFeature";
|
||||||
menuBuilder << "RicDuplicateSummaryCrossPlotCurveFeature";
|
menuBuilder << "RicDuplicateSummaryCrossPlotCurveFeature";
|
||||||
menuBuilder << "Separator";
|
menuBuilder << "Separator";
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.h
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.h
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.h
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryDeclineCurve.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCE_GROUP_SOURCE_FILES
|
set(SOURCE_GROUP_SOURCE_FILES
|
||||||
@@ -104,6 +105,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTable.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableCollection.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryTableTools.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RimSummaryDeclineCurve.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
||||||
|
|||||||
@@ -1233,19 +1233,31 @@ void RimSummaryCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField,
|
|||||||
|
|
||||||
if ( loadAndUpdate )
|
if ( loadAndUpdate )
|
||||||
{
|
{
|
||||||
this->loadDataAndUpdate( true );
|
loadAndUpdateDataAndPlot();
|
||||||
|
|
||||||
plot->updateAxes();
|
|
||||||
plot->updatePlotTitle();
|
|
||||||
plot->updateConnectedEditors();
|
|
||||||
|
|
||||||
RiuPlotMainWindow* mainPlotWindow = RiaGuiApplication::instance()->mainPlotWindow();
|
|
||||||
mainPlotWindow->updateMultiPlotToolBar();
|
|
||||||
|
|
||||||
dataChanged.send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ public:
|
|||||||
~RimSummaryCurve() override;
|
~RimSummaryCurve() override;
|
||||||
|
|
||||||
// Y Axis functions
|
// Y Axis functions
|
||||||
RiaSummaryCurveDefinition curveDefinitionY() const;
|
RiaSummaryCurveDefinition curveDefinitionY() const;
|
||||||
RimSummaryCase* summaryCaseY() const;
|
RimSummaryCase* summaryCaseY() const;
|
||||||
RifEclipseSummaryAddress summaryAddressY() const;
|
RifEclipseSummaryAddress summaryAddressY() const;
|
||||||
std::string unitNameY() const;
|
std::string unitNameY() const;
|
||||||
std::vector<double> valuesY() const;
|
virtual std::vector<double> valuesY() const;
|
||||||
|
|
||||||
void applyCurveDefinitionY( const RiaSummaryCurveDefinition& curveDefinition );
|
void applyCurveDefinitionY( const RiaSummaryCurveDefinition& curveDefinition );
|
||||||
void setSummaryCaseY( RimSummaryCase* sumCase );
|
void setSummaryCaseY( RimSummaryCase* sumCase );
|
||||||
@@ -65,20 +65,20 @@ public:
|
|||||||
void setSummaryAddressY( const RifEclipseSummaryAddress& address );
|
void setSummaryAddressY( const RifEclipseSummaryAddress& address );
|
||||||
void setResampling( RiaDefines::DateTimePeriodEnum resampling );
|
void setResampling( RiaDefines::DateTimePeriodEnum resampling );
|
||||||
|
|
||||||
RifEclipseSummaryAddress errorSummaryAddressY() const;
|
RifEclipseSummaryAddress errorSummaryAddressY() const;
|
||||||
std::vector<double> errorValuesY() const;
|
std::vector<double> errorValuesY() const;
|
||||||
void setLeftOrRightAxisY( RiuPlotAxis plotAxis );
|
void setLeftOrRightAxisY( RiuPlotAxis plotAxis );
|
||||||
RiuPlotAxis axisY() const;
|
RiuPlotAxis axisY() const;
|
||||||
std::vector<time_t> timeStepsY() const;
|
virtual std::vector<time_t> timeStepsY() const;
|
||||||
double yValueAtTimeT( time_t time ) const;
|
double yValueAtTimeT( time_t time ) const;
|
||||||
void setOverrideCurveDataY( const std::vector<time_t>& xValues, const std::vector<double>& yValues );
|
void setOverrideCurveDataY( const std::vector<time_t>& xValues, const std::vector<double>& yValues );
|
||||||
|
|
||||||
// X Axis functions
|
// X Axis functions
|
||||||
RiaSummaryCurveDefinition curveDefinitionX() const;
|
RiaSummaryCurveDefinition curveDefinitionX() const;
|
||||||
RimSummaryCase* summaryCaseX() const;
|
RimSummaryCase* summaryCaseX() const;
|
||||||
RifEclipseSummaryAddress summaryAddressX() const;
|
RifEclipseSummaryAddress summaryAddressX() const;
|
||||||
std::string unitNameX() const;
|
std::string unitNameX() const;
|
||||||
std::vector<double> valuesX() const;
|
virtual std::vector<double> valuesX() const;
|
||||||
|
|
||||||
void setSummaryCaseX( RimSummaryCase* sumCase );
|
void setSummaryCaseX( RimSummaryCase* sumCase );
|
||||||
void setSummaryAddressX( const RifEclipseSummaryAddress& address );
|
void setSummaryAddressX( const RifEclipseSummaryAddress& address );
|
||||||
@@ -105,18 +105,15 @@ protected:
|
|||||||
void updateZoomInParentPlot() override;
|
void updateZoomInParentPlot() override;
|
||||||
void onLoadDataAndUpdate( bool updateParentPlot ) override;
|
void onLoadDataAndUpdate( bool updateParentPlot ) override;
|
||||||
|
|
||||||
|
void loadAndUpdateDataAndPlot();
|
||||||
|
|
||||||
void updateLegendsInPlot() override;
|
void updateLegendsInPlot() override;
|
||||||
|
|
||||||
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override;
|
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override;
|
||||||
void initAfterRead() override;
|
void initAfterRead() override;
|
||||||
double computeCurveZValue() override;
|
double computeCurveZValue() override;
|
||||||
|
|
||||||
private:
|
virtual std::vector<time_t> timeStepsX() const;
|
||||||
RifSummaryReaderInterface* valuesSummaryReaderX() const;
|
|
||||||
RifSummaryReaderInterface* valuesSummaryReaderY() const;
|
|
||||||
std::vector<time_t> timeStepsX() const;
|
|
||||||
|
|
||||||
void calculateCurveInterpolationFromAddress();
|
|
||||||
|
|
||||||
// Overridden PDM methods
|
// Overridden PDM methods
|
||||||
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
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 defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||||
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) 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<caf::PdmOptionItemInfo>* options, RimSummaryCase* summaryCase );
|
static void appendOptionItemsForSummaryAddresses( QList<caf::PdmOptionItemInfo>* options, RimSummaryCase* summaryCase );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// 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 <QDateTime>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
CAF_PDM_SOURCE_INIT( RimSummaryDeclineCurve, "DeclineCurve" );
|
||||||
|
|
||||||
|
namespace caf
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
void caf::AppEnum<RimSummaryDeclineCurve::DeclineCurveType>::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<double> RimSummaryDeclineCurve::valuesY() const
|
||||||
|
{
|
||||||
|
return createDeclineCurveValues( RimSummaryCurve::valuesY(),
|
||||||
|
RimSummaryCurve::timeStepsY(),
|
||||||
|
RiaSummaryTools::hasAccumulatedData( summaryAddressY() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<double> RimSummaryDeclineCurve::valuesX() const
|
||||||
|
{
|
||||||
|
return createDeclineCurveValues( RimSummaryCurve::valuesX(),
|
||||||
|
RimSummaryCurve::timeStepsX(),
|
||||||
|
RiaSummaryTools::hasAccumulatedData( summaryAddressX() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<time_t> RimSummaryDeclineCurve::timeStepsY() const
|
||||||
|
{
|
||||||
|
std::vector<time_t> timeSteps = RimSummaryCurve::timeStepsY();
|
||||||
|
appendFutureTimeSteps( timeSteps );
|
||||||
|
return timeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<time_t> RimSummaryDeclineCurve::timeStepsX() const
|
||||||
|
{
|
||||||
|
std::vector<time_t> timeSteps = RimSummaryCurve::timeStepsX();
|
||||||
|
appendFutureTimeSteps( timeSteps );
|
||||||
|
return timeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<double> RimSummaryDeclineCurve::createDeclineCurveValues( const std::vector<double>& values,
|
||||||
|
const std::vector<time_t>& 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<QDateTime> futureTimeSteps = createFutureTimeSteps( timeSteps );
|
||||||
|
std::vector<double> 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<double, double> RimSummaryDeclineCurve::computeInitialProductionAndDeclineRate( const std::vector<double>& values,
|
||||||
|
const std::vector<time_t>& 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<size_t>( 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<size_t>( 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<QDateTime> RimSummaryDeclineCurve::createFutureTimeSteps( const std::vector<time_t>& 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<time_t>& timeSteps ) const
|
||||||
|
{
|
||||||
|
std::set<QDateTime> futureTimeSteps = createFutureTimeSteps( timeSteps );
|
||||||
|
appendTimeSteps( timeSteps, futureTimeSteps );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
void RimSummaryDeclineCurve::appendTimeSteps( std::vector<time_t>& timeSteps, const std::set<QDateTime>& 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<caf::PdmUiLineEditorAttribute*>( 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<caf::PdmUiDoubleSliderEditorAttribute*>( 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";
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cafPdmField.h"
|
||||||
|
#include "cafPdmObject.h"
|
||||||
|
|
||||||
|
#include "RimSummaryCurve.h"
|
||||||
|
|
||||||
|
#include "cafAppEnum.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
///
|
||||||
|
///
|
||||||
|
//==================================================================================================
|
||||||
|
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<double> valuesY() const override;
|
||||||
|
std::vector<time_t> timeStepsY() const override;
|
||||||
|
|
||||||
|
// X Axis functions
|
||||||
|
std::vector<double> valuesX() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString createCurveAutoName() override;
|
||||||
|
|
||||||
|
std::vector<time_t> 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<time_t>& timeSteps ) const;
|
||||||
|
|
||||||
|
std::vector<double>
|
||||||
|
createDeclineCurveValues( const std::vector<double>& values, const std::vector<time_t>& timeSteps, bool isAccumulatedResult ) const;
|
||||||
|
|
||||||
|
std::set<QDateTime> createFutureTimeSteps( const std::vector<time_t>& timeSteps ) const;
|
||||||
|
static void appendTimeSteps( std::vector<time_t>& timeSteps, const std::set<QDateTime>& moreTimeSteps );
|
||||||
|
|
||||||
|
static std::pair<double, double> computeInitialProductionAndDeclineRate( const std::vector<double>& values,
|
||||||
|
const std::vector<time_t>& timeSteps,
|
||||||
|
bool isAccumulatedResult );
|
||||||
|
|
||||||
|
double computePredictedValue( double initialProductionRate, double initialDeclineRate, double timeSinceStart, bool isAccumulatedResult ) const;
|
||||||
|
|
||||||
|
caf::PdmField<caf::AppEnum<DeclineCurveType>> m_declineCurveType;
|
||||||
|
caf::PdmField<int> m_predictionYears;
|
||||||
|
caf::PdmField<double> m_hyperbolicDeclineConstant;
|
||||||
|
};
|
||||||
@@ -181,6 +181,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RigWellAllocationOverTime.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigWellAllocationOverTime.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigWellResultBranch.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigWellResultBranch.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigWellResultFrame.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigWellResultFrame.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RigDeclineCurveCalculator.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "RigDeclineCurveCalculator.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
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 ) );
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// 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 );
|
||||||
|
};
|
||||||
@@ -90,6 +90,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RifRevealCsvSummaryReader-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RifRevealCsvSummaryReader-Test.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RifStimPlanCsvSummaryReader-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RifStimPlanCsvSummaryReader-Test.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RiaEnsembleNameTools-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RiaEnsembleNameTools-Test.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RigDeclineCurveCalculator-Test.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(RESINSIGHT_ENABLE_GRPC)
|
if(RESINSIGHT_ENABLE_GRPC)
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/gpl.html>
|
||||||
|
// 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 );
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user