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;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
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 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>
|
||||
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}/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})
|
||||
|
@@ -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 << "RicNewSummaryCurveFeature";
|
||||
menuBuilder << "RicDuplicateSummaryCurveFeature";
|
||||
menuBuilder << "RicCreateDeclineCurvesFeature";
|
||||
menuBuilder << "RicNewSummaryCrossPlotCurveFeature";
|
||||
menuBuilder << "RicDuplicateSummaryCrossPlotCurveFeature";
|
||||
menuBuilder << "Separator";
|
||||
|
@@ -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})
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@@ -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<double> valuesY() const;
|
||||
RiaSummaryCurveDefinition curveDefinitionY() const;
|
||||
RimSummaryCase* summaryCaseY() const;
|
||||
RifEclipseSummaryAddress summaryAddressY() const;
|
||||
std::string unitNameY() const;
|
||||
virtual std::vector<double> 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<double> errorValuesY() const;
|
||||
void setLeftOrRightAxisY( RiuPlotAxis plotAxis );
|
||||
RiuPlotAxis axisY() const;
|
||||
std::vector<time_t> timeStepsY() const;
|
||||
double yValueAtTimeT( time_t time ) const;
|
||||
void setOverrideCurveDataY( const std::vector<time_t>& xValues, const std::vector<double>& yValues );
|
||||
RifEclipseSummaryAddress errorSummaryAddressY() const;
|
||||
std::vector<double> errorValuesY() const;
|
||||
void setLeftOrRightAxisY( RiuPlotAxis plotAxis );
|
||||
RiuPlotAxis axisY() const;
|
||||
virtual std::vector<time_t> timeStepsY() const;
|
||||
double yValueAtTimeT( time_t time ) const;
|
||||
void setOverrideCurveDataY( const std::vector<time_t>& xValues, const std::vector<double>& yValues );
|
||||
|
||||
// X Axis functions
|
||||
RiaSummaryCurveDefinition curveDefinitionX() const;
|
||||
RimSummaryCase* summaryCaseX() const;
|
||||
RifEclipseSummaryAddress summaryAddressX() const;
|
||||
std::string unitNameX() const;
|
||||
std::vector<double> valuesX() const;
|
||||
RiaSummaryCurveDefinition curveDefinitionX() const;
|
||||
RimSummaryCase* summaryCaseX() const;
|
||||
RifEclipseSummaryAddress summaryAddressX() const;
|
||||
std::string unitNameX() const;
|
||||
virtual std::vector<double> 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<time_t> timeStepsX() const;
|
||||
|
||||
void calculateCurveInterpolationFromAddress();
|
||||
virtual std::vector<time_t> 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<caf::PdmOptionItemInfo>* options, RimSummaryCase* summaryCase );
|
||||
|
||||
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}/RigWellResultBranch.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RigWellResultFrame.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RigDeclineCurveCalculator.cpp
|
||||
)
|
||||
|
||||
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}/RifStimPlanCsvSummaryReader-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiaEnsembleNameTools-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RigDeclineCurveCalculator-Test.cpp
|
||||
)
|
||||
|
||||
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