Merge branch 'dev' into fishbones

This commit is contained in:
Bjørnar Grip Fjær 2017-06-01 09:02:38 +02:00
commit 1aedf92efc
33 changed files with 6868 additions and 177 deletions

View File

@ -322,3 +322,46 @@ exceptions:
CRAVA is a software package for seismic inversion and conditioning of CRAVA is a software package for seismic inversion and conditioning of
geological reservoir models. CRAVA is copyrighted by the Norwegian geological reservoir models. CRAVA is copyrighted by the Norwegian
Computing Center and Statoil and licensed under GPLv3+. Computing Center and Statoil and licensed under GPLv3+.
===============================================================================
Notice for opm-flowdiagnostics and opm-flowdiagnostics-applications libraries
===============================================================================
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
===============================================================================
Notice for the NightCharts code
===============================================================================
NightCharts
Copyright (C) 2010 by Alexander A. Avdonin, Artem N. Ivanov / ITGears Co.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Please contact gordos.kund@gmail.com with any questions on this license.

View File

@ -22,8 +22,10 @@
#include "RiaPreferences.h" #include "RiaPreferences.h"
#include "RifEclipseSummaryAddress.h" #include "RifEclipseSummaryAddress.h"
#include "RifReaderEclipseSummary.h"
#include "RigSingleWellResultsData.h" #include "RigSingleWellResultsData.h"
#include "RigSummaryCaseData.h"
#include "RimEclipseResultCase.h" #include "RimEclipseResultCase.h"
#include "RimEclipseWell.h" #include "RimEclipseWell.h"
@ -33,7 +35,7 @@
#include "RimProject.h" #include "RimProject.h"
#include "RimSummaryCaseCollection.h" #include "RimSummaryCaseCollection.h"
#include "RimSummaryCurve.h" #include "RimSummaryCurve.h"
#include "RimSummaryCurveFilter.h" #include "RimSummaryCurveAppearanceCalculator.h"
#include "RimSummaryPlot.h" #include "RimSummaryPlot.h"
#include "RimSummaryPlotCollection.h" #include "RimSummaryPlotCollection.h"
#include "RimView.h" #include "RimView.h"
@ -94,67 +96,92 @@ void RicPlotProductionRateFeature::onActionTriggered(bool isChecked)
RimGridSummaryCase* gridSummaryCase = RicPlotProductionRateFeature::gridSummaryCaseForWell(well); RimGridSummaryCase* gridSummaryCase = RicPlotProductionRateFeature::gridSummaryCaseForWell(well);
if (!gridSummaryCase) continue; if (!gridSummaryCase) continue;
QString curveFilterText = "W*PR:";
QString description = "Well Production Rates : "; QString description = "Well Production Rates : ";
RigSingleWellResultsData* wRes = well->wellResults(); if (isInjector(well))
if (wRes)
{ {
RimView* rimView = nullptr; description = "Well Injection Rates : ";
well->firstAncestorOrThisOfTypeAsserted(rimView);
int currentTimeStep = rimView->currentTimeStep();
if (wRes->hasWellResult(currentTimeStep))
{
const RigWellResultFrame& wrf = wRes->wellResultFrame(currentTimeStep);
if ( wrf.m_productionType == RigWellResultFrame::OIL_INJECTOR
|| wrf.m_productionType == RigWellResultFrame::GAS_INJECTOR
|| wrf.m_productionType == RigWellResultFrame::WATER_INJECTOR)
{
curveFilterText = "W*IR:";
description = "Well Injection Rates : ";
}
}
} }
curveFilterText += well->name();
description += well->name();
RimSummaryPlot* plot = new RimSummaryPlot(); RimSummaryPlot* plot = new RimSummaryPlot();
summaryPlotColl->summaryPlots().push_back(plot); summaryPlotColl->summaryPlots().push_back(plot);
description += well->name();
plot->setDescription(description); plot->setDescription(description);
if (isInjector(well))
{ {
RimSummaryCurveFilter* newCurveFilter = new RimSummaryCurveFilter(); // Left Axis
plot->addCurveFilter(newCurveFilter);
newCurveFilter->createCurves(gridSummaryCase, curveFilterText); RimDefines::PlotAxis plotAxis = RimDefines::PLOT_AXIS_LEFT;
{
// Note : The parameter "WOIR" is probably never-existing, but we check for existence before creating curve
// Oil
QString parameterName = "WOIR";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledGreenColor(0));
}
{
// Water
QString parameterName = "WWIR";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledBlueColor(0));
}
{
// Gas
QString parameterName = "WGIR";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledRedColor(0));
}
}
else
{
// Left Axis
RimDefines::PlotAxis plotAxis = RimDefines::PLOT_AXIS_LEFT;
{
// Oil
QString parameterName = "WOPR";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledGreenColor(0));
}
{
// Water
QString parameterName = "WWPR";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledBlueColor(0));
}
{
// Gas
QString parameterName = "WGPR";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledRedColor(0));
}
} }
// Right Axis
{ {
RimSummaryCurve* newCurve = new RimSummaryCurve(); RimDefines::PlotAxis plotAxis = RimDefines::PLOT_AXIS_RIGHT;
plot->addCurve(newCurve);
newCurve->setSummaryCase(gridSummaryCase); {
QString parameterName = "WTHP";
RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
plotAxis, RimSummaryCurveAppearanceCalculator::cycledNoneRGBBrColor(0));
}
RifEclipseSummaryAddress addr( RifEclipseSummaryAddress::SUMMARY_WELL, {
"WBHP", QString parameterName = "WBHP";
-1, RicPlotProductionRateFeature::addSummaryCurve(plot, well, gridSummaryCase, parameterName,
-1, plotAxis, RimSummaryCurveAppearanceCalculator::cycledNoneRGBBrColor(1));
"", }
well->name().toStdString(),
-1,
"",
-1,
-1,
-1);
newCurve->setSummaryAddress(addr);
newCurve->setYAxis(RimDefines::PlotAxis::PLOT_AXIS_RIGHT);
} }
summaryPlotColl->updateConnectedEditors(); summaryPlotColl->updateConnectedEditors();
@ -210,3 +237,71 @@ RimGridSummaryCase* RicPlotProductionRateFeature::gridSummaryCaseForWell(RimEcli
return nullptr; return nullptr;
} }
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicPlotProductionRateFeature::isInjector(RimEclipseWell* well)
{
RigSingleWellResultsData* wRes = well->wellResults();
if (wRes)
{
RimView* rimView = nullptr;
well->firstAncestorOrThisOfTypeAsserted(rimView);
int currentTimeStep = rimView->currentTimeStep();
if (wRes->hasWellResult(currentTimeStep))
{
const RigWellResultFrame& wrf = wRes->wellResultFrame(currentTimeStep);
if ( wrf.m_productionType == RigWellResultFrame::OIL_INJECTOR
|| wrf.m_productionType == RigWellResultFrame::GAS_INJECTOR
|| wrf.m_productionType == RigWellResultFrame::WATER_INJECTOR)
{
return true;
}
}
}
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimSummaryCurve* RicPlotProductionRateFeature::addSummaryCurve( RimSummaryPlot* plot, const RimEclipseWell* well,
RimGridSummaryCase* gridSummaryCase, const QString& vectorName,
RimDefines::PlotAxis plotAxis, const cvf::Color3f& color)
{
CVF_ASSERT(plot);
CVF_ASSERT(gridSummaryCase);
CVF_ASSERT(well);
RifEclipseSummaryAddress addr(RifEclipseSummaryAddress::SUMMARY_WELL,
vectorName.toStdString(),
-1,
-1,
"",
well->name().toStdString(),
-1,
"",
-1,
-1,
-1);
if (!gridSummaryCase->caseData()->summaryReader()->hasAddress(addr))
{
return nullptr;
}
RimSummaryCurve* newCurve = new RimSummaryCurve();
plot->addCurve(newCurve);
newCurve->setSummaryCase(gridSummaryCase);
newCurve->setSummaryAddress(addr);
newCurve->setColor(color);
newCurve->setYAxis(plotAxis);
return newCurve;
}

View File

@ -21,9 +21,12 @@
#include "cafCmdFeature.h" #include "cafCmdFeature.h"
#include "RimFlowDiagSolution.h" #include "RimFlowDiagSolution.h"
#include "RimDefines.h"
class RimGridSummaryCase; class RimGridSummaryCase;
class RimEclipseWell; class RimEclipseWell;
class RimSummaryPlot;
class RimSummaryCurve;
//================================================================================================== //==================================================================================================
/// ///
@ -39,7 +42,11 @@ protected:
virtual void setupActionLook( QAction* actionToSetup ) override; virtual void setupActionLook( QAction* actionToSetup ) override;
private: private:
static RimGridSummaryCase* gridSummaryCaseForWell(RimEclipseWell* well); static RimGridSummaryCase* gridSummaryCaseForWell(RimEclipseWell* well);
static bool isInjector(RimEclipseWell* well);
static RimSummaryCurve* addSummaryCurve(RimSummaryPlot* plot, const RimEclipseWell* well,
RimGridSummaryCase* gridSummaryCase, const QString& vectorName,
RimDefines::PlotAxis plotAxis, const cvf::Color3f& color);
}; };

View File

@ -23,6 +23,8 @@
#include "RimEclipseResultCase.h" #include "RimEclipseResultCase.h"
#include "RimEclipseView.h" #include "RimEclipseView.h"
#include "RimFlowCharacteristicsPlot.h" #include "RimFlowCharacteristicsPlot.h"
#include "RigFlowDiagResults.h"
#include "RimFlowDiagSolution.h"
#include "RimFlowPlotCollection.h" #include "RimFlowPlotCollection.h"
#include "RimMainPlotCollection.h" #include "RimMainPlotCollection.h"
#include "RimProject.h" #include "RimProject.h"
@ -71,6 +73,16 @@ void RicShowFlowCharacteristicsPlotFeature::onActionTriggered(bool isChecked)
if (eclCase && eclCase->defaultFlowDiagSolution()) if (eclCase && eclCase->defaultFlowDiagSolution())
{ {
// Make sure flow results for the the active timestep is calculated, to avoid an empty plot
{
RimView * activeView = RiaApplication::instance()->activeReservoirView();
if (activeView && eclCase->defaultFlowDiagSolution()->flowDiagResults())
{
// Trigger calculation
eclCase->defaultFlowDiagSolution()->flowDiagResults()->maxAbsPairFlux(activeView->currentTimeStep());
}
}
if (RiaApplication::instance()->project()) if (RiaApplication::instance()->project())
{ {
RimFlowPlotCollection* flowPlotColl = RiaApplication::instance()->project()->mainPlotCollection->flowPlotCollection(); RimFlowPlotCollection* flowPlotColl = RiaApplication::instance()->project()->mainPlotCollection->flowPlotCollection();

View File

@ -971,13 +971,22 @@ RigWellResultPoint RifReaderEclipseOutput::createWellResultPoint(const RigGridBa
resultPoint.m_oilRate = oilRate; resultPoint.m_oilRate = oilRate;
resultPoint.m_waterRate = waterRate; resultPoint.m_waterRate = waterRate;
/// Unit conversion for use with Well Allocation plots
// Convert Gas to oil equivalents
// If field unit, the Gas is in Mega ft^3 while the others are in [stb] (barrel) // If field unit, the Gas is in Mega ft^3 while the others are in [stb] (barrel)
// Unused Gas to Barrel conversion
// we convert gas to stb as well. Based on // we convert gas to stb as well. Based on
// 1 [stb] = 0.15898729492800007 [m^3] // 1 [stb] = 0.15898729492800007 [m^3]
// 1 [ft] = 0.3048 [m] // 1 [ft] = 0.3048 [m]
// megaFt3ToStbFactor = 1.0 / (1.0e-6 * 0.15898729492800007 * ( 1.0 / 0.3048 )^3 ) // megaFt3ToStbFactor = 1.0 / (1.0e-6 * 0.15898729492800007 * ( 1.0 / 0.3048 )^3 )
double megaFt3ToStbFactor = 178107.60668; // double megaFt3ToStbFactor = 178107.60668;
if (m_eclipseCase->unitsType() == RigEclipseCaseData::UNITS_FIELD) gasRate = megaFt3ToStbFactor * gasRate;
double fieldGasToOilEquivalent = 1.0e6/5800; // Mega ft^3 to BOE
double metricGasToOilEquivalent = 1.0/1.0e3; // Sm^3 Gas to Sm^3 oe
if (m_eclipseCase->unitsType() == RigEclipseCaseData::UNITS_FIELD) gasRate = fieldGasToOilEquivalent * gasRate;
if (m_eclipseCase->unitsType() == RigEclipseCaseData::UNITS_METRIC) gasRate = metricGasToOilEquivalent * gasRate;
resultPoint.m_gasRate = gasRate; resultPoint.m_gasRate = gasRate;
} }

View File

@ -141,14 +141,16 @@ QList<caf::PdmOptionItemInfo> RimFlowCharacteristicsPlot::calculateValueOptions(
{ {
std::vector<RimEclipseResultCase*> cases; std::vector<RimEclipseResultCase*> cases;
proj->descendantsIncludingThisOfType(cases); proj->descendantsIncludingThisOfType(cases);
RimEclipseResultCase* defaultCase = nullptr;
for ( RimEclipseResultCase* c : cases ) for ( RimEclipseResultCase* c : cases )
{ {
if ( c->defaultFlowDiagSolution() ) if ( c->defaultFlowDiagSolution() )
{ {
options.push_back(caf::PdmOptionItemInfo(c->caseUserDescription(), c, false, c->uiIcon())); options.push_back(caf::PdmOptionItemInfo(c->caseUserDescription(), c, false, c->uiIcon()));
if (!defaultCase) defaultCase = c; // Select first
} }
} }
if (!m_case() && defaultCase) m_case = defaultCase;
} }
} }
else if ( fieldNeedingOptions == &m_flowDiagSolution ) else if ( fieldNeedingOptions == &m_flowDiagSolution )

View File

@ -357,29 +357,46 @@ std::map<QString, const std::vector<double> *> RimWellAllocationPlot::findReleva
void RimWellAllocationPlot::updateWellFlowPlotXAxisTitle(RimWellLogTrack* plotTrack) void RimWellAllocationPlot::updateWellFlowPlotXAxisTitle(RimWellLogTrack* plotTrack)
{ {
RigEclipseCaseData::UnitsType unitSet = m_case->eclipseCaseData()->unitsType(); RigEclipseCaseData::UnitsType unitSet = m_case->eclipseCaseData()->unitsType();
QString unitText;
switch ( unitSet )
{
case RigEclipseCaseData::UNITS_METRIC:
unitText = "[m^3/day]";
break;
case RigEclipseCaseData::UNITS_FIELD:
unitText = "[Brl/day]";
break;
case RigEclipseCaseData::UNITS_LAB:
unitText = "[cm^3/hr]";
break;
default:
break;
}
if (m_flowDiagSolution) if (m_flowDiagSolution)
{ {
QString unitText;
switch ( unitSet )
{
case RigEclipseCaseData::UNITS_METRIC:
unitText = "[m<sup>3</sup>/day]";
break;
case RigEclipseCaseData::UNITS_FIELD:
unitText = "[Brl/day]";
break;
case RigEclipseCaseData::UNITS_LAB:
unitText = "[cm<sup>3</sup>/hr]";
break;
default:
break;
}
plotTrack->setXAxisTitle("Reservoir Flow Rate " + unitText); plotTrack->setXAxisTitle("Reservoir Flow Rate " + unitText);
} }
else else
{ {
QString unitText;
switch ( unitSet )
{
case RigEclipseCaseData::UNITS_METRIC:
unitText = "[Liquid Sm<sup>3</sup>/day], [Gas kSm<sup>3</sup>/day]";
break;
case RigEclipseCaseData::UNITS_FIELD:
unitText = "[Liquid BBL/day], [Gas BOE/day]";
break;
case RigEclipseCaseData::UNITS_LAB:
unitText = "[cm<sup>3</sup>/hr]";
break;
default:
break;
}
plotTrack->setXAxisTitle("Surface Flow Rate " + unitText); plotTrack->setXAxisTitle("Surface Flow Rate " + unitText);
} }
} }

View File

@ -55,19 +55,18 @@ public:
void setupCurveLook(RimSummaryCurve* curve); void setupCurveLook(RimSummaryCurve* curve);
private: static cvf::Color3f cycledPaletteColor(int colorIndex);
static cvf::Color3f cycledNoneRGBBrColor(int colorIndex);
static cvf::Color3f cycledGreenColor(int colorIndex);
static cvf::Color3f cycledBlueColor(int colorIndex);
static cvf::Color3f cycledRedColor(int colorIndex);
static cvf::Color3f cycledBrownColor(int colorIndex);
private:
void setOneCurveAppearance(CurveAppearanceType appeaType, size_t totalCount, int appeaIdx, RimSummaryCurve* curve); void setOneCurveAppearance(CurveAppearanceType appeaType, size_t totalCount, int appeaIdx, RimSummaryCurve* curve);
void updateApperanceIndices(); void updateApperanceIndices();
std::map<std::string, size_t> mapNameToAppearanceIndex(CurveAppearanceType & appearance, const std::set<std::string>& names); std::map<std::string, size_t> mapNameToAppearanceIndex(CurveAppearanceType & appearance, const std::set<std::string>& names);
cvf::Color3f cycledPaletteColor(int colorIndex);
cvf::Color3f cycledNoneRGBBrColor(int colorIndex);
cvf::Color3f cycledGreenColor(int colorIndex);
cvf::Color3f cycledBlueColor(int colorIndex);
cvf::Color3f cycledRedColor(int colorIndex);
cvf::Color3f cycledBrownColor(int colorIndex);
RimPlotCurve::LineStyleEnum cycledLineStyle(int index); RimPlotCurve::LineStyleEnum cycledLineStyle(int index);
RimPlotCurve::PointSymbolEnum cycledSymbol(int index); RimPlotCurve::PointSymbolEnum cycledSymbol(int index);

View File

@ -74,7 +74,6 @@ public:
const std::vector<double>& pseudoLengthFromTop(size_t branchIdx) const; const std::vector<double>& pseudoLengthFromTop(size_t branchIdx) const;
const std::vector<double>& trueVerticalDepth(size_t branchIdx) const; const std::vector<double>& trueVerticalDepth(size_t branchIdx) const;
const std::vector<double>& accumulatedTracerFlowPrPseudoLength(const QString& tracerName, size_t branchIdx) const; const std::vector<double>& accumulatedTracerFlowPrPseudoLength(const QString& tracerName, size_t branchIdx) const;
const std::vector<double>& flowPrPseudoLength( size_t branchIdx) const;
const std::vector<double>& tracerFlowPrPseudoLength(const QString& tracerName, size_t branchIdx) const; const std::vector<double>& tracerFlowPrPseudoLength(const QString& tracerName, size_t branchIdx) const;

View File

@ -21,7 +21,7 @@ namespace RigFlowDiagInterfaceTools {
template <class FluxCalc> template <class FluxCalc>
inline Opm::FlowDiagnostics::ConnectionValues inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G, extractFluxField(const Opm::ECLGraph& G,
FluxCalc&& getFlux) FluxCalc&& getFlux)
{ {
using ConnVals = Opm::FlowDiagnostics::ConnectionValues; using ConnVals = Opm::FlowDiagnostics::ConnectionValues;
@ -52,24 +52,11 @@ namespace RigFlowDiagInterfaceTools {
} }
inline Opm::FlowDiagnostics::ConnectionValues inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G, extractFluxFieldFromRestartFile(const Opm::ECLGraph& G,
const Opm::ECLRestartData& rstrt, const Opm::ECLRestartData& rstrt)
const bool compute_fluxes)
{ {
if (compute_fluxes) {
Opm::ECLFluxCalc calc(G);
auto getFlux = [&calc, &rstrt]
(const Opm::ECLGraph::PhaseIndex p)
{
return calc.flux(rstrt, p);
};
return extractFluxField(G, getFlux);
}
auto getFlux = [&G, &rstrt] auto getFlux = [&G, &rstrt]
(const Opm::ECLGraph::PhaseIndex p) (const Opm::ECLPhaseIndex p)
{ {
return G.flux(rstrt, p); return G.flux(rstrt, p);
}; };

View File

@ -251,9 +251,8 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
Opm::FlowDiagnostics::CellSetValues sumWellFluxPrCell; Opm::FlowDiagnostics::CellSetValues sumWellFluxPrCell;
{ {
Opm::FlowDiagnostics::ConnectionValues connectionsVals = RigFlowDiagInterfaceTools::extractFluxField(*(m_opmFlowDiagStaticData->m_eclGraph), Opm::FlowDiagnostics::ConnectionValues connectionsVals = RigFlowDiagInterfaceTools::extractFluxFieldFromRestartFile(*(m_opmFlowDiagStaticData->m_eclGraph),
*currentRestartData, *currentRestartData);
false);
m_opmFlowDiagStaticData->m_fldToolbox->assignConnectionFlux(connectionsVals); m_opmFlowDiagStaticData->m_fldToolbox->assignConnectionFlux(connectionsVals);
@ -400,7 +399,8 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
{ {
Graph flowCapStorCapCurve = flowCapacityStorageCapacityCurve(*(injectorSolution.get()), Graph flowCapStorCapCurve = flowCapacityStorageCapacityCurve(*(injectorSolution.get()),
*(producerSolution.get()), *(producerSolution.get()),
m_opmFlowDiagStaticData->m_poreVolume); m_opmFlowDiagStaticData->m_poreVolume,
0.1);
result.setFlowCapStorageCapCurve(flowCapStorCapCurve); result.setFlowCapStorageCapCurve(flowCapStorCapCurve);
result.setSweepEfficiencyCurve(sweepEfficiency(flowCapStorCapCurve)); result.setSweepEfficiencyCurve(sweepEfficiency(flowCapStorCapCurve));

View File

@ -1,7 +1,7 @@
set(RESINSIGHT_MAJOR_VERSION 2016) set(RESINSIGHT_MAJOR_VERSION 2017)
set(RESINSIGHT_MINOR_VERSION 11) set(RESINSIGHT_MINOR_VERSION 05)
set(RESINSIGHT_INCREMENT_VERSION "flow.14") set(RESINSIGHT_INCREMENT_VERSION "1-dev")
# https://github.com/CRAVA/crava/tree/master/libs/nrlib # https://github.com/CRAVA/crava/tree/master/libs/nrlib
@ -11,10 +11,10 @@ set(NRLIB_GITHUB_SHA "ba35d4359882f1c6f5e9dc30eb95fe52af50fd6f")
set(ERT_GITHUB_SHA "06a39878636af0bc52582430ad0431450e51139c") set(ERT_GITHUB_SHA "06a39878636af0bc52582430ad0431450e51139c")
# https://github.com/OPM/opm-flowdiagnostics # https://github.com/OPM/opm-flowdiagnostics
set(OPM_FLOWDIAGNOSTICS_SHA "2c5fb55db4c4ded49c14161dd16463e1207da049") set(OPM_FLOWDIAGNOSTICS_SHA "b6e59ddcd2feba450c8612a7402c9239e442c0d4")
# https://github.com/OPM/opm-flowdiagnostics-applications # https://github.com/OPM/opm-flowdiagnostics-applications
set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "570601718e7197b751bc3cba60c1e5fb7d842842") set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "ccaaa4dd1b553e131a3051687fd615fe728b76ee")
# https://github.com/OPM/opm-parser/blob/master/opm/parser/eclipse/Units/Units.hpp # https://github.com/OPM/opm-parser/blob/master/opm/parser/eclipse/Units/Units.hpp
# This file was moved from opm-core to opm-parser october 2016 # This file was moved from opm-core to opm-parser october 2016

View File

@ -21,14 +21,19 @@
# the library needs it. # the library needs it.
list (APPEND MAIN_SOURCE_FILES list (APPEND MAIN_SOURCE_FILES
opm/utility/ECLEndPointScaling.cpp
opm/utility/ECLFluxCalc.cpp opm/utility/ECLFluxCalc.cpp
opm/utility/ECLGraph.cpp opm/utility/ECLGraph.cpp
opm/utility/ECLPropTable.cpp
opm/utility/ECLResultData.cpp opm/utility/ECLResultData.cpp
opm/utility/ECLSaturationFunc.cpp
opm/utility/ECLUnitHandling.cpp opm/utility/ECLUnitHandling.cpp
opm/utility/ECLWellSolution.cpp opm/utility/ECLWellSolution.cpp
) )
list (APPEND TEST_SOURCE_FILES list (APPEND TEST_SOURCE_FILES
tests/test_eclendpointscaling.cpp
tests/test_eclproptable.cpp
tests/test_eclunithandling.cpp tests/test_eclunithandling.cpp
) )
@ -44,9 +49,13 @@ list (APPEND EXAMPLE_SOURCE_FILES
) )
list (APPEND PUBLIC_HEADER_FILES list (APPEND PUBLIC_HEADER_FILES
opm/utility/ECLEndPointScaling.hpp
opm/utility/ECLFluxCalc.hpp opm/utility/ECLFluxCalc.hpp
opm/utility/ECLGraph.hpp opm/utility/ECLGraph.hpp
opm/utility/ECLPhaseIndex.hpp
opm/utility/ECLPropTable.hpp
opm/utility/ECLResultData.hpp opm/utility/ECLResultData.hpp
opm/utility/ECLSaturationFunc.hpp
opm/utility/ECLUnitHandling.hpp opm/utility/ECLUnitHandling.hpp
opm/utility/ECLWellSolution.hpp opm/utility/ECLWellSolution.hpp
) )

View File

@ -38,34 +38,27 @@ try {
std::vector<Opm::FlowDiagnostics::CellSet> start; std::vector<Opm::FlowDiagnostics::CellSet> start;
auto fwd = fdTool.computeInjectionDiagnostics(start); auto fwd = fdTool.computeInjectionDiagnostics(start);
auto rev = fdTool.computeProductionDiagnostics(start); auto rev = fdTool.computeProductionDiagnostics(start);
// Give disconnected cells zero pore volume.
std::vector<int> nbcount(setup.graph.numCells(), 0);
for (int nb : setup.graph.neighbours()) {
if (nb >= 0) {
++nbcount[nb];
}
}
auto pv = setup.graph.poreVolume(); auto pv = setup.graph.poreVolume();
for (size_t i = 0; i < pv.size(); ++i) {
if (nbcount[i] == 0) {
pv[i] = 0.0;
}
}
// Cells that have more than 100 times the average pore volume are const bool ignore_disconnected = setup.param.getDefault("ignore_disconnected", true);
// probably aquifers, we ignore them (again by setting pore volume if (ignore_disconnected) {
// to zero). If this is the correct thing to do or not could // Give disconnected cells zero pore volume.
// depend on what you want to use the diagnostic for. std::vector<int> nbcount(setup.graph.numCells(), 0);
const double average_pv = std::accumulate(pv.begin(), pv.end(), 0.0) / double(pv.size()); for (int nb : setup.graph.neighbours()) {
for (double& pvval : pv) { if (nb >= 0) {
if (pvval > 100.0 * average_pv) { ++nbcount[nb];
pvval = 0.0; }
}
for (size_t i = 0; i < pv.size(); ++i) {
if (nbcount[i] == 0) {
pv[i] = 0.0;
}
} }
} }
// Compute graph. // Compute graph.
auto fphi = Opm::FlowDiagnostics::flowCapacityStorageCapacityCurve(fwd, rev, pv); const double max_pv_fraction = setup.param.getDefault("max_pv_fraction", 0.1);
auto fphi = Opm::FlowDiagnostics::flowCapacityStorageCapacityCurve(fwd, rev, pv, max_pv_fraction);
// Write it to standard out. // Write it to standard out.
std::cout.precision(16); std::cout.precision(16);

View File

@ -31,6 +31,7 @@
#include <opm/utility/ECLFluxCalc.hpp> #include <opm/utility/ECLFluxCalc.hpp>
#include <opm/utility/ECLGraph.hpp> #include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLResultData.hpp> #include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLWellSolution.hpp> #include <opm/utility/ECLWellSolution.hpp>
@ -115,15 +116,19 @@ namespace example {
} }
inline Opm::FlowDiagnostics::ConnectionValues inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G, extractFluxField(const Opm::ECLGraph& G,
const Opm::ECLRestartData& rstrt, const Opm::ECLInitFileData& init,
const bool compute_fluxes) const Opm::ECLRestartData& rstrt,
const bool compute_fluxes,
const bool useEPS)
{ {
if (compute_fluxes) { if (compute_fluxes) {
Opm::ECLFluxCalc calc(G); auto satfunc = ::Opm::ECLSaturationFunc(G, init, useEPS);
Opm::ECLFluxCalc calc(G, std::move(satfunc));
auto getFlux = [&calc, &rstrt] auto getFlux = [&calc, &rstrt]
(const Opm::ECLGraph::PhaseIndex p) (const Opm::ECLPhaseIndex p)
{ {
return calc.flux(rstrt, p); return calc.flux(rstrt, p);
}; };
@ -132,7 +137,7 @@ namespace example {
} }
auto getFlux = [&G, &rstrt] auto getFlux = [&G, &rstrt]
(const Opm::ECLGraph::PhaseIndex p) (const Opm::ECLPhaseIndex p)
{ {
return G.flux(rstrt, p); return G.flux(rstrt, p);
}; };
@ -205,17 +210,6 @@ namespace example {
inline Opm::ECLGraph
initGraph(const FilePaths& file_paths)
{
const auto I = Opm::ECLInitFileData(file_paths.init);
return Opm::ECLGraph::load(file_paths.grid, I);
}
inline Opm::FlowDiagnostics::Toolbox inline Opm::FlowDiagnostics::Toolbox
initToolbox(const Opm::ECLGraph& G) initToolbox(const Opm::ECLGraph& G)
{ {
@ -238,11 +232,13 @@ namespace example {
Setup(int argc, char** argv) Setup(int argc, char** argv)
: param (initParam(argc, argv)) : param (initParam(argc, argv))
, file_paths (param) , file_paths (param)
, init (file_paths.init)
, rstrt (file_paths.restart) , rstrt (file_paths.restart)
, graph (initGraph(file_paths)) , graph (::Opm::ECLGraph::load(file_paths.grid, init))
, well_fluxes () , well_fluxes ()
, toolbox (initToolbox(graph)) , toolbox (initToolbox(graph))
, compute_fluxes_(param.getDefault("compute_fluxes", false)) , compute_fluxes_(param.getDefault("compute_fluxes", false))
, useEPS_ (param.getDefault("use_ep_scaling", false))
{ {
const int step = param.getDefault("step", 0); const int step = param.getDefault("step", 0);
@ -268,7 +264,9 @@ namespace example {
well_fluxes = wsol.solution(rstrt, graph.activeGrids()); well_fluxes = wsol.solution(rstrt, graph.activeGrids());
} }
toolbox.assignConnectionFlux(extractFluxField(graph, rstrt, compute_fluxes_)); toolbox.assignConnectionFlux(extractFluxField(graph, init, rstrt,
compute_fluxes_, useEPS_));
toolbox.assignInflowFlux(extractWellFlows(graph, well_fluxes)); toolbox.assignInflowFlux(extractWellFlows(graph, well_fluxes));
return true; return true;
@ -276,11 +274,13 @@ namespace example {
Opm::ParameterGroup param; Opm::ParameterGroup param;
FilePaths file_paths; FilePaths file_paths;
Opm::ECLInitFileData init;
Opm::ECLRestartData rstrt; Opm::ECLRestartData rstrt;
Opm::ECLGraph graph; Opm::ECLGraph graph;
std::vector<Opm::ECLWellSolution::WellData> well_fluxes; std::vector<Opm::ECLWellSolution::WellData> well_fluxes;
Opm::FlowDiagnostics::Toolbox toolbox; Opm::FlowDiagnostics::Toolbox toolbox;
bool compute_fluxes_ = false; bool compute_fluxes_ = false;
bool useEPS_ = false;
}; };

View File

@ -0,0 +1,431 @@
/*
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ECLENDPOINTSCALING_HEADER_INCLUDED
#define OPM_ECLENDPOINTSCALING_HEADER_INCLUDED
#include <opm/utility/ECLPhaseIndex.hpp>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace Opm {
class ECLGraph;
class ECLInitFileData;
} // namespace Opm
namespace Opm { namespace SatFunc {
/// Protocol for computing scaled saturation values.
class EPSEvalInterface
{
public:
/// Static (unscaled) end-points of a particular, tabulated
/// saturation function.
struct TableEndPoints {
/// Critical or connate/minimum saturation, depending on
/// subsequent function evaluation context.
///
/// Use critical saturation when computing look-up values for
/// relative permeability and connate/minimum saturation when
/// computing look-up values for capillary pressure.
double low;
/// Displacing saturation (3-pt option only).
double disp;
/// Maximum (high) saturation point.
double high;
};
/// Associate a saturation value to a specific cell.
struct SaturationAssoc {
/// Cell to which to connect a saturation value.
std::vector<int>::size_type cell;
/// Saturation value.
double sat;
};
/// Convenience type alias.
using SaturationPoints = std::vector<SaturationAssoc>;
/// Derive scaled saturations--inputs to subsequent evaluation of
/// saturation functions--corresponding to a sequence of unscaled
/// saturation values.
///
/// \param[in] tep Static end points that identify the saturation
/// scaling intervals of a particular tabulated saturation
/// function.
///
/// \param[in] sp Sequence of saturation points.
///
/// \return Sequence of scaled saturation values in order of the
/// input sequence. In particular the \c i-th element of this
/// result is the scaled version of \code sp[i].sat \endcode.
virtual std::vector<double>
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const = 0;
virtual std::unique_ptr<EPSEvalInterface> clone() const = 0;
/// Destructor. Must be virtual.
virtual ~EPSEvalInterface();
};
/// Implementation of ECLIPSE's standard, two-point, saturation scaling
/// option.
class TwoPointScaling : public EPSEvalInterface
{
public:
/// Constructor.
///
/// Typically set up to define the end-point scaling of all active
/// cells in a model, but could alternatively be used as a means to
/// computing the effective saturation function of a single cell.
///
/// \param[in] smin Left end points for a set of cells.
///
/// \param[in] smax Right end points for a set of cells.
TwoPointScaling(std::vector<double> smin,
std::vector<double> smax);
/// Destructor.
~TwoPointScaling();
/// Copy constructor.
///
/// \param[in] rhs Existing object.
TwoPointScaling(const TwoPointScaling& rhs);
/// Move constructor.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing object.
TwoPointScaling(TwoPointScaling&& rhs);
/// Assignment operator.
///
/// Replaces current implementation with a copy of existing object's
/// implementation details.
///
/// \param[in] rhs Existing object.
///
/// \return \code *this \endcode.
TwoPointScaling&
operator=(const TwoPointScaling& rhs);
/// Move assingment operator.
///
/// Subsumes existing object's implementation details and uses those
/// to replace the current implementation.
///
/// \return \code *this \endcode.
TwoPointScaling&
operator=(TwoPointScaling&& rhs);
/// Derive scaled saturations using the two-point scaling definition
/// from a sequence of unscaled saturation values.
///
/// \param[in] tep Static end points that identify the saturation
/// scaling intervals of a particular tabulated saturation
/// function. The evaluation procedure considers only \code
/// tep.low \endcode and \code tep.high \endcode. The value of
/// \code tep.disp \endcode is never read.
///
/// \param[in] sp Sequence of saturation points. The maximum cell
/// index (\code sp[i].cell \endcode) must be strictly less than
/// the size of the input arrays that define the current
/// saturation regions.
///
/// \return Sequence of scaled saturation values in order of the
/// input sequence. In particular the \c i-th element of this
/// result is the scaled version of \code sp[i].sat \endcode.
virtual std::vector<double>
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const override;
virtual std::unique_ptr<EPSEvalInterface> clone() const override;
private:
/// Implementation class.
class Impl;
/// Pimpl idiom.
std::unique_ptr<Impl> pImpl_;
};
/// Implementation of ECLIPSE's alternative, three-point, saturation
/// scaling option.
class ThreePointScaling : public EPSEvalInterface
{
public:
/// Constructor.
///
/// Typically set up to define the end-point scaling of all active
/// cells in a model, but could alternatively be used as a means to
/// computing the effective saturation function of a single cell.
///
/// \param[in] smin Left end points for a set of cells.
///
/// \param[in] sdisp Intermediate--displacing saturation--end points
/// for a set of cells.
///
/// \param[in] smax Right end points for a set of cells.
ThreePointScaling(std::vector<double> smin,
std::vector<double> sdisp,
std::vector<double> smax);
/// Destructor.
~ThreePointScaling();
/// Copy constructor.
///
/// \param[in] rhs Existing object.
ThreePointScaling(const ThreePointScaling& rhs);
/// Move constructor.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing object.
ThreePointScaling(ThreePointScaling&& rhs);
/// Assignment operator.
///
/// Replaces current implementation with a copy of existing object's
/// implementation details.
///
/// \param[in] rhs Existing object.
///
/// \return \code *this \endcode.
ThreePointScaling&
operator=(const ThreePointScaling& rhs);
/// Move assingment operator.
///
/// Subsumes existing object's implementation details and uses those
/// to replace the current implementation.
///
/// \return \code *this \endcode.
ThreePointScaling&
operator=(ThreePointScaling&& rhs);
/// Derive scaled saturations using the three-point scaling
/// definition from a sequence of unscaled saturation values.
///
/// \param[in] tep Static end points that identify the saturation
/// scaling intervals of a particular tabulated saturation
/// function. The evaluation procedure considers only \code
/// tep.low \endcode and \code tep.high \endcode. The value of
/// \code tep.disp \endcode is never read.
///
/// \param[in] sp Sequence of saturation points. The maximum cell
/// index (\code sp[i].cell \endcode) must be strictly less than
/// the size of the input arrays that define the current
/// saturation regions.
///
/// \return Sequence of scaled saturation values in order of the
/// input sequence. In particular the \c i-th element of this
/// result is the scaled version of \code sp[i].sat \endcode.
virtual std::vector<double>
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const override;
virtual std::unique_ptr<EPSEvalInterface> clone() const override;
private:
/// Implementation class.
class Impl;
/// Pimpl idiom.
std::unique_ptr<Impl> pImpl_;
};
/// Named constructors for enabling saturation end-point scaling from an
/// ECL result set (see class \c ECLInitFileData).
struct CreateEPS
{
/// Category of function for which to create an EPS evaluator.
enum class FunctionCategory {
/// This EPS is for relative permeability. Possibly uses
/// three-point (alternative) formulation.
Relperm,
/// This EPS is for capillary pressure. Two-point option only.
CapPress,
};
/// Categories of saturation function systems for which to create an
/// EPS evaluator.
enum class SubSystem {
/// Create an EPS for a curve in the Oil-Water (sub-) system.
OilWater,
/// Create an EPS for a curve in the Oil-Gas (sub-) system.
OilGas,
};
/// Set of options that uniquely define a single EPS operation.
struct EPSOptions {
/// Whether or not to employ the alternative (i.e., 3-pt) scaling
/// procedure. Only applicable to FunctionCategory::Relperm and
/// ignored in the case of FunctionCategory::CapPress.
bool use3PtScaling;
/// Curve-type for which to create an EPS.
FunctionCategory curve;
/// Part of global fluid system for which to create an EPS.
SubSystem subSys;
/// Phase for whose \c curve in which \c subSys to create an
/// EPS.
///
/// Example: Create a standard (two-point) EPS for the relative
/// permeability of oil in the oil-gas subsystem of an
/// oil-gas-water active phase system.
///
/// \code
/// auto opt = EPSOptions{};
///
/// opt.use3PtScaling = false;
/// opt.curve = FunctionCategory::Relperm;
/// opt.subSys = SubSystem::OilGas;
/// opt.thisPh = ECLPhaseIndex::Oil;
///
/// auto eps = CreateEPS::fromECLOutput(G, init, opt);
/// \endcode
::Opm::ECLPhaseIndex thisPh;
};
/// Collection of raw saturation table end points.
struct RawTableEndPoints {
/// Collection of connate (minimum) saturation end points.
struct Connate {
/// Connate oil saturation for each table in total set of
/// tabulated saturation functions.
std::vector<double> oil;
/// Connate gas saturation for each table in total set of
/// tabulated saturation functions.
std::vector<double> gas;
/// Connate water saturation for each table in total set of
/// tabulated saturation functions.
std::vector<double> water;
};
/// Collection of critical saturations. Used in deriving scaled
/// displacing saturations in the alternative (three-point)
/// scaling procedure.
struct Critical {
/// Critical oil saturation in 2p OG system from total set
/// of tabulated saturation functions.
std::vector<double> oil_in_gas;
/// Critical oil saturation in 2p OW system from total set
/// of tabulated saturation functions.
std::vector<double> oil_in_water;
/// Critical gas saturation in 2p OG or 3p OGW system from
/// total set of tabulated saturation functions.
std::vector<double> gas;
/// Critical water saturation in 2p OW or 3p OGW system from
/// total set of tabulated saturation functions.
std::vector<double> water;
};
/// Collection of maximum saturation end points.
struct Maximum {
/// Maximum oil saturation for each table in total set of
/// tabulated saturation functions.
std::vector<double> oil;
/// Maximum gas saturation for each table in total set of
/// tabulated saturation functions.
std::vector<double> gas;
/// Maximum water saturation for each table in total set of
/// tabulated saturation functions.
std::vector<double> water;
};
/// Minimum saturation end points for all tabulated saturation
/// functions.
Connate conn;
/// Critical saturations for all tabulated saturation functions.
Critical crit;
/// Maximum saturation end points for all tabulated saturation
/// functions.
Maximum smax;
};
/// Construct an EPS evaluator from a particular ECL result set.
///
/// \param[in] G Connected topology of current model's active cells.
/// Needed to linearise table end-points that may be distributed
/// on local grids to all of the model's active cells (\code
/// member function G.rawLinearisedCellData() \endcode).
///
/// \param[in] init Container of tabulated saturation functions and
/// saturation table end points for all active cells.
///
/// \param[in] opt Options that identify a particular end-point
/// scaling behaviour of a particular saturation function curve.
///
/// \return EPS evaluator for the particular curve defined by the
/// input options.
static std::unique_ptr<EPSEvalInterface>
fromECLOutput(const ECLGraph& G,
const ECLInitFileData& init,
const EPSOptions& opt);
/// Extract table end points relevant to a particular EPS evaluator
/// from raw tabulated saturation functions.
///
/// \param[in] ep Collection of all raw table saturation end points
/// for all tabulated saturation functions. Typically computed
/// by direct calls to the \code connateSat() \endcode, \code
/// criticalSat() \endcode, and \code maximumSat() \endcode of
/// the currently active \code Opm::SatFuncInterpolant \code
/// objects.
///
/// \param[in] opt Options that identify a particular end-point
/// scaling behaviour of a particular saturation function curve.
///
/// \return Subset of the input end points in a format intended for
/// passing as the first argument of member function \code eval()
/// \endcode of the \code EPSEvalInterface \endcode that
/// corresponds to the input options.
static std::vector<EPSEvalInterface::TableEndPoints>
unscaledEndPoints(const RawTableEndPoints& ep,
const EPSOptions& opt);
};
}} // namespace Opm::SatFunc
#endif // OPM_ECLENDPOINTSCALING_HEADER_INCLUDED

View File

@ -22,11 +22,15 @@
#include <opm/parser/eclipse/Units/Units.hpp> #include <opm/parser/eclipse/Units/Units.hpp>
#include <utility>
namespace Opm namespace Opm
{ {
ECLFluxCalc::ECLFluxCalc(const ECLGraph& graph) ECLFluxCalc::ECLFluxCalc(const ECLGraph& graph,
ECLSaturationFunc&& satfunc)
: graph_(graph) : graph_(graph)
, satfunc_(std::move(satfunc))
, neighbours_(graph.neighbours()) , neighbours_(graph.neighbours())
, transmissibility_(graph.transmissibility()) , transmissibility_(graph.transmissibility())
{ {
@ -38,7 +42,7 @@ namespace Opm
std::vector<double> std::vector<double>
ECLFluxCalc::flux(const ECLRestartData& rstrt, ECLFluxCalc::flux(const ECLRestartData& rstrt,
const PhaseIndex /* phase */) const const ECLPhaseIndex phase) const
{ {
// Obtain dynamic data. // Obtain dynamic data.
DynamicData dyn_data; DynamicData dyn_data;
@ -46,6 +50,9 @@ namespace Opm
.linearisedCellData(rstrt, "PRESSURE", .linearisedCellData(rstrt, "PRESSURE",
&ECLUnits::UnitSystem::pressure); &ECLUnits::UnitSystem::pressure);
dyn_data.relperm = this->satfunc_
.relperm(this->graph_, rstrt, phase);
// Compute fluxes per connection. // Compute fluxes per connection.
const int num_conn = transmissibility_.size(); const int num_conn = transmissibility_.size();
std::vector<double> fluxvec(num_conn); std::vector<double> fluxvec(num_conn);
@ -66,8 +73,12 @@ namespace Opm
const int c2 = neighbours_[2*connection + 1]; const int c2 = neighbours_[2*connection + 1];
const double transmissibility = transmissibility_[connection]; const double transmissibility = transmissibility_[connection];
const double viscosity = 1.0 * prefix::centi * unit::Poise; const double viscosity = 1.0 * prefix::centi * unit::Poise;
const double mobility = 1.0 / viscosity;
const auto& pressure = dyn_data.pressure; const auto& pressure = dyn_data.pressure;
const int upwind_cell = (pressure[c2] > pressure[c1]) ? c2 : c1;
const double kr = dyn_data.relperm[upwind_cell];
const double mobility = kr / viscosity;
return mobility * transmissibility * (pressure[c1] - pressure[c2]); return mobility * transmissibility * (pressure[c1] - pressure[c2]);
} }

View File

@ -21,6 +21,9 @@
#define OPM_ECLFLUXCALC_HEADER_INCLUDED #define OPM_ECLFLUXCALC_HEADER_INCLUDED
#include <opm/utility/ECLGraph.hpp> #include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLSaturationFunc.hpp>
#include <vector> #include <vector>
namespace Opm namespace Opm
@ -34,13 +37,15 @@ namespace Opm
/// Construct from ECLGraph. /// Construct from ECLGraph.
/// ///
/// \param[in] graph Connectivity data, as well as providing a means to read data from the restart file. /// \param[in] graph Connectivity data, as well as providing a means to read data from the restart file.
explicit ECLFluxCalc(const ECLGraph& graph); explicit ECLFluxCalc(const ECLGraph& graph,
ECLSaturationFunc&& satfunc);
using PhaseIndex = ECLGraph::PhaseIndex;
/// Retrive phase flux on all connections defined by \code /// Retrive phase flux on all connections defined by \code
/// graph.neighbours() \endcode. /// graph.neighbours() \endcode.
/// ///
/// \param[in] rstrt ECL Restart data set from which to extract
/// relevant data per cell.
///
/// \param[in] phase Canonical phase for which to retrive flux. /// \param[in] phase Canonical phase for which to retrive flux.
/// ///
/// \return Flux values corresponding to selected phase. /// \return Flux values corresponding to selected phase.
@ -48,18 +53,20 @@ namespace Opm
/// Numerical values in SI units (rm^3/s). /// Numerical values in SI units (rm^3/s).
std::vector<double> std::vector<double>
flux(const ECLRestartData& rstrt, flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const; const ECLPhaseIndex phase) const;
private: private:
struct DynamicData struct DynamicData
{ {
std::vector<double> pressure; std::vector<double> pressure;
std::vector<double> relperm;
}; };
double singleFlux(const int connection, double singleFlux(const int connection,
const DynamicData& dyn_data) const; const DynamicData& dyn_data) const;
const ECLGraph& graph_; const ECLGraph& graph_;
ECLSaturationFunc satfunc_;
std::vector<int> neighbours_; std::vector<int> neighbours_;
std::vector<double> transmissibility_; std::vector<double> transmissibility_;
}; };

View File

@ -1278,7 +1278,7 @@ public:
/// ///
/// Mostly useful to determine the set of \c PhaseIndex values for which /// Mostly useful to determine the set of \c PhaseIndex values for which
/// flux() may return non-zero values. /// flux() may return non-zero values.
const std::vector<PhaseIndex>& activePhases() const; const std::vector<ECLPhaseIndex>& activePhases() const;
/// Retrieve the simulation scenario's set of active grids. /// Retrieve the simulation scenario's set of active grids.
/// ///
@ -1322,7 +1322,7 @@ public:
/// all). /// all).
std::vector<double> std::vector<double>
flux(const ECLRestartData& rstrt, flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const; const ECLPhaseIndex phase) const;
/// Retrieve result set vector from current view linearised on active /// Retrieve result set vector from current view linearised on active
/// cells. /// cells.
@ -1609,7 +1609,7 @@ private:
/// Set of active phases in result set. Derived from .INIT on the /// Set of active phases in result set. Derived from .INIT on the
/// assumption that the set of active phases does not change throughout /// assumption that the set of active phases does not change throughout
/// the simulation run. /// the simulation run.
std::vector<PhaseIndex> activePhases_; std::vector<ECLPhaseIndex> activePhases_;
/// Set of active grids in result set. /// Set of active grids in result set.
std::vector<std::string> activeGrids_; std::vector<std::string> activeGrids_;
@ -1639,7 +1639,7 @@ private:
/// \return Basename for ECl vector corresponding to particular phase /// \return Basename for ECl vector corresponding to particular phase
/// flux. /// flux.
std::string std::string
flowVector(const PhaseIndex phase) const; flowVector(const ECLPhaseIndex phase) const;
/// Extract flux values corresponding to particular result set vector /// Extract flux values corresponding to particular result set vector
/// for all identified non-neighbouring connections. /// for all identified non-neighbouring connections.
@ -1940,7 +1940,7 @@ Opm::ECLGraph::Impl::numConnections() const
return nconn + this->nnc_.numConnections(); return nconn + this->nnc_.numConnections();
} }
const std::vector<Opm::ECLGraph::PhaseIndex>& const std::vector<Opm::ECLPhaseIndex>&
Opm::ECLGraph::Impl::activePhases() const Opm::ECLGraph::Impl::activePhases() const
{ {
return this->activePhases_; return this->activePhases_;
@ -2028,7 +2028,7 @@ Opm::ECLGraph::Impl::transmissibility() const
std::vector<double> std::vector<double>
Opm::ECLGraph::Impl::flux(const ECLRestartData& rstrt, Opm::ECLGraph::Impl::flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const const ECLPhaseIndex phase) const
{ {
auto fluxUnit = [&rstrt](const std::string& gridID) auto fluxUnit = [&rstrt](const std::string& gridID)
{ {
@ -2226,12 +2226,12 @@ defineActivePhases(const ::Opm::ECLInitFileData& init)
const auto phaseMask = const auto phaseMask =
static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]); static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]);
using Check = std::pair<PhaseIndex, unsigned int>; using Check = std::pair<ECLPhaseIndex, unsigned int>;
const auto allPhases = std::vector<Check> { const auto allPhases = std::vector<Check> {
{ PhaseIndex::Aqua , (1u << 1) }, { ECLPhaseIndex::Aqua , (1u << 1) },
{ PhaseIndex::Liquid, (1u << 0) }, { ECLPhaseIndex::Liquid, (1u << 0) },
{ PhaseIndex::Vapour, (1u << 2) }, { ECLPhaseIndex::Vapour, (1u << 2) },
}; };
this->activePhases_.clear(); this->activePhases_.clear();
@ -2243,19 +2243,19 @@ defineActivePhases(const ::Opm::ECLInitFileData& init)
} }
std::string std::string
Opm::ECLGraph::Impl::flowVector(const PhaseIndex phase) const Opm::ECLGraph::Impl::flowVector(const ECLPhaseIndex phase) const
{ {
const auto vector = std::string("FLR"); // Flow-rate, reservoir const auto vector = std::string("FLR"); // Flow-rate, reservoir
if (phase == PhaseIndex::Aqua) { if (phase == ECLPhaseIndex::Aqua) {
return vector + "WAT"; return vector + "WAT";
} }
if (phase == PhaseIndex::Liquid) { if (phase == ECLPhaseIndex::Liquid) {
return vector + "OIL"; return vector + "OIL";
} }
if (phase == PhaseIndex::Vapour) { if (phase == ECLPhaseIndex::Vapour) {
return vector + "GAS"; return vector + "GAS";
} }
@ -2322,7 +2322,7 @@ std::size_t Opm::ECLGraph::numConnections() const
return this->pImpl_->numConnections(); return this->pImpl_->numConnections();
} }
const std::vector<Opm::ECLGraph::PhaseIndex>& const std::vector<Opm::ECLPhaseIndex >&
Opm::ECLGraph::activePhases() const Opm::ECLGraph::activePhases() const
{ {
return this->pImpl_->activePhases(); return this->pImpl_->activePhases();
@ -2351,7 +2351,7 @@ std::vector<double> Opm::ECLGraph::transmissibility() const
std::vector<double> std::vector<double>
Opm::ECLGraph::flux(const ECLRestartData& rstrt, Opm::ECLGraph::flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const const ECLPhaseIndex phase) const
{ {
return this->pImpl_->flux(rstrt, phase); return this->pImpl_->flux(rstrt, phase);
} }

View File

@ -20,6 +20,7 @@
#ifndef OPM_ECLGRAPH_HEADER_INCLUDED #ifndef OPM_ECLGRAPH_HEADER_INCLUDED
#define OPM_ECLGRAPH_HEADER_INCLUDED #define OPM_ECLGRAPH_HEADER_INCLUDED
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLResultData.hpp> #include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp> #include <opm/utility/ECLUnitHandling.hpp>
@ -45,9 +46,6 @@ namespace Opm {
class ECLGraph class ECLGraph
{ {
public: public:
/// Enum for indicating requested phase from the flux() method.
enum class PhaseIndex { Aqua = 0, Liquid = 1, Vapour = 2 };
/// Disabled default constructor. /// Disabled default constructor.
ECLGraph() = delete; ECLGraph() = delete;
@ -125,7 +123,7 @@ namespace Opm {
/// ///
/// Mostly useful to determine the set of \c PhaseIndex values for /// Mostly useful to determine the set of \c PhaseIndex values for
/// which flux() will return non-zero values if data available. /// which flux() will return non-zero values if data available.
const std::vector<PhaseIndex>& activePhases() const; const std::vector<ECLPhaseIndex>& activePhases() const;
/// Retrieve the simulation scenario's set of active grids. /// Retrieve the simulation scenario's set of active grids.
/// ///
@ -167,7 +165,7 @@ namespace Opm {
/// (rm^3/s). /// (rm^3/s).
std::vector<double> std::vector<double>
flux(const ECLRestartData& rstrt, flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const; const ECLPhaseIndex phase) const;
/// Retrieve result set vector from current view (e.g., particular /// Retrieve result set vector from current view (e.g., particular
/// report step) linearised on active cells. /// report step) linearised on active cells.

View File

@ -0,0 +1,32 @@
/*
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ECLPHASEINDEX_HEADER_INCLUDED
#define OPM_ECLPHASEINDEX_HEADER_INCLUDED
namespace Opm {
/// Enum for indicating the phase--or set of phases--on which to apply a
/// phase-dependent operation (e.g., extracting flux data from a result
/// set or computing relative permeabilities from tabulated functions).
enum class ECLPhaseIndex { Aqua = 0, Liquid = 1, Vapour = 2 };
} // namespace Opm
#endif // OPM_ECLPHASEINDEX_HEADER_INCLUDED

View File

@ -0,0 +1,305 @@
/*
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <opm/utility/ECLPropTable.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <exception>
#include <iterator>
#include <stdexcept>
#include <utility>
Opm::SatFuncInterpolant::SingleTable::
SingleTable(ElmIt xBegin,
ElmIt xEnd,
std::vector<ElmIt>& colIt)
{
// There must be at least one dependent variable/result variable.
assert (colIt.size() >= 1);
const auto nRows = std::distance(xBegin, xEnd);
this->x_.reserve(nRows);
this->y_.reserve(nRows * colIt.size());
auto keyValid = [](const double xi)
{
// Indep. variable values <= -1.0e20 or >= 1.0e20 signal "unused"
// table nodes (rows). These nodes are in the table to fill out the
// allocated size if one particular sub-table does not use all
// nodes. The magic value 1.0e20 is documented in the Fileformats
// Reference Manual.
return std::abs(xi) < 1.0e20;
};
while (xBegin != xEnd) {
// Extract relevant portion of the table. Preallocated rows that
// are not actually part of the result set (i.e., those that are set
// to a sentinel value) are discarded.
if (keyValid(*xBegin)) {
this->x_.push_back(*xBegin);
for (auto ci : colIt) {
// Store 'y_' with column index cycling most rapidly.
this->y_.push_back(*ci);
}
}
// -------------------------------------------------------------
// Advance iterators.
// 1) Independent variable.
++xBegin;
// 2) Dependent/result/columns.
for (auto& ci : colIt) {
++ci;
}
}
// Dispose of any excess capacity.
if (this->x_.size() < static_cast<decltype(this->x_.size())>(nRows)) {
this->x_.shrink_to_fit();
this->y_.shrink_to_fit();
}
if (this->x_.size() < 2) {
// Table has no interval that supports interpolation. Either just a
// single node or no nodes at all. We can't do anything useful
// here, so don't pretend that this is okay.
throw std::invalid_argument {
"No Interpolation Intervals of Non-Zero Size"
};
}
}
double
Opm::SatFuncInterpolant::SingleTable::
y(const ECLPropTableRawData::SizeType nCols,
const ECLPropTableRawData::SizeType row,
const ResultColumn& c) const
{
assert (row * nCols < this->y_.size());
assert (c.i < nCols);
// Recall: 'y_' stored with column index cycling the most rapidly (row
// major ordering).
return this->y_[row*nCols + c.i];
}
std::vector<double>
Opm::SatFuncInterpolant::SingleTable::
interpolate(const ECLPropTableRawData::SizeType nCols,
const ResultColumn& c,
const std::vector<double>& x) const
{
auto y = std::vector<double>{}; y.reserve(x.size());
auto yval = [nCols, c, this]
(const ECLPropTableRawData::SizeType i)
{
return this->y(nCols, i, c);
};
const auto yfirst =
yval(ECLPropTableRawData::SizeType{ 0 });
const auto ylast =
yval(ECLPropTableRawData::SizeType{ this->x_.size() - 1 });
for (const auto& xi : x) {
y.push_back(0.0);
auto& yi = y.back();
if (! (xi > this->x_.front())) {
// Constant extrapolation to the left of range.
yi = yfirst;
}
else if (! (xi < this->x_.back())) {
// Constant extrapolation to the right of range.
yi = ylast;
}
else {
// Somewhere in [min(x_), max(x_)]. Primary key (indep. var) is
// sorted range. Recall: lower_bound() returns insertion point,
// which translates to the *upper* (right-hand) end-point of the
// interval in this context.
auto b = std::begin(this->x_);
auto p = std::lower_bound(b, std::end(this->x_), xi);
assert ((p != b) && "Logic Error Left End-Point");
assert ((p != std::end(this->x_)) &&
"Logic Error Right End-Point");
// p = lower_bound() => left == i-1, right == i-0.
const auto i = p - b;
const auto left = i - 1;
const auto right = i - 0;
const auto xl = this->x_[left];
const auto t = (xi - xl) / (this->x_[right] - xl);
yi = (1.0 - t)*yval(left) + t*yval(right);
}
}
return y;
}
double
Opm::SatFuncInterpolant::SingleTable::connateSat() const
{
return this->x_.front();
}
double
Opm::SatFuncInterpolant::SingleTable::
criticalSat(const ECLPropTableRawData::SizeType nCols,
const ResultColumn& c) const
{
// Note: Relative permeability functions are presented as non-decreasing
// functions of the corresponding phase saturation. The internal table
// format essentially mirrors that of input deck keywords SWFN, SGFN,
// and SOF* (i.e., saturation function family II). Extracting the
// critical saturation--even for oil--therefore amounts to a forward,
// linear scan from row=0 to row=n-1 irrespective of the input format of
// the current saturation function.
const auto nRows = this->x_.size();
auto row = 0 * nRows;
for (; row < nRows; ++row) {
if (this->y(nCols, row, c) > 0.0) { break; }
}
if (row == 0) {
throw std::invalid_argument {
"Table Does Not Define Critical Saturation"
};
}
return this->x_[row - 1];
}
double
Opm::SatFuncInterpolant::SingleTable::maximumSat() const
{
return this->x_.back();
}
// =====================================================================
Opm::SatFuncInterpolant::SatFuncInterpolant(const ECLPropTableRawData& raw)
: nResCols_(raw.numCols - 1)
{
if (raw.numCols < 2) {
throw std::invalid_argument {
"Malformed Property Table"
};
}
this->table_.reserve(raw.numTables);
// Table format: numRows*numTables values of first column (indep. var)
// followed by numCols-1 dependent variable (function value result)
// columns of numRows*numTables values each, one column at a time.
const auto colStride = raw.numRows * raw.numTables;
// Position column iterators (independent variable and results
// respectively) at beginning of each pertinent table column.
auto xBegin = std::begin(raw.data);
auto colIt = std::vector<decltype(xBegin)>{ xBegin + colStride };
for (auto col = 0*raw.numCols + 1; col < raw.numCols - 1; ++col) {
colIt.push_back(colIt.back() + colStride);
}
for (auto t = 0*raw.numTables;
t < raw.numTables;
++t, xBegin += raw.numRows)
{
auto xEnd = xBegin + raw.numRows;
// Note: The SingleTable ctor advances each 'colIt' across numRows
// entries. That is a bit of a layering violation, but helps in the
// implementation of this loop.
this->table_.push_back(SingleTable(xBegin, xEnd, colIt));
}
}
std::vector<double>
Opm::SatFuncInterpolant::interpolate(const InTable& t,
const ResultColumn& c,
const std::vector<double>& x) const
{
if (t.i >= this->table_.size()) {
throw std::invalid_argument {
"Invalid Table ID"
};
}
if (c.i >= this->nResCols_) {
throw std::invalid_argument {
"Invalid Result Column ID"
};
}
return this->table_[t.i].interpolate(this->nResCols_, c, x);
}
std::vector<double>
Opm::SatFuncInterpolant::connateSat() const
{
auto sconn = std::vector<double>{};
sconn.reserve(this->table_.size());
for (const auto& t : this->table_) {
sconn.push_back(t.connateSat());
}
return sconn;
}
std::vector<double>
Opm::SatFuncInterpolant::criticalSat(const ResultColumn& c) const
{
auto scrit = std::vector<double>{};
scrit.reserve(this->table_.size());
for (const auto& t : this->table_) {
scrit.push_back(t.criticalSat(this->nResCols_, c));
}
return scrit;
}
std::vector<double>
Opm::SatFuncInterpolant::maximumSat() const
{
auto smax = std::vector<double>{};
smax.reserve(this->table_.size());
for (const auto& t : this->table_) {
smax.push_back(t.maximumSat());
}
return smax;
}

View File

@ -0,0 +1,182 @@
/*
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ECLPROPTABLE_HEADER_INCLUDED
#define OPM_ECLPROPTABLE_HEADER_INCLUDED
#include <vector>
/// \file
///
/// ECL Tabulated Functions (e.g., saturation functions).
namespace Opm {
/// Raw table data from which to construct collection of interpolants.
struct ECLPropTableRawData
{
/// Representation of the raw table data. 1D array with implicit
/// substructure.
using DataVector = std::vector<double>;
/// Size type for subsets of table data.
using SizeType = DataVector::size_type;
/// Iterator to table elements. Must be random access.
using ElementIterator = DataVector::const_iterator;
/// Raw table data. Column major (Fortran) order. Typically
/// copied/extracted directly from TAB vector of INIT result-set.
DataVector data;
/// Number of rows allocated in the result set for each individual
/// table. Typically corresponds to setting in one of the *DIMS
/// keywords. Should normally be at least two.
SizeType numRows;
/// Number of columns in this table. Varies by keyword/table.
SizeType numCols;
/// Number of tables of this type. Must match the corresponding
/// region keyword.
SizeType numTables;
};
/// Collection of 1D interpolants from tabulated functions (e.g., the
/// saturation functions).
class SatFuncInterpolant
{
public:
/// Constructor.
///
/// \param[in] raw Raw table data for this collection.
explicit SatFuncInterpolant(const ECLPropTableRawData& raw);
/// Wrapper type to disambiguate API usage. Represents a table ID.
struct InTable {
/// Table ID.
ECLPropTableRawData::SizeType i;
};
/// Wrapper type to disambiguate API usage. Represents a column ID.
struct ResultColumn {
/// Column ID.
ECLPropTableRawData::SizeType i;
};
/// Evaluate 1D interpolant in sequence of points.
///
/// \param[in] t ID of sub-table of interpolant.
///
/// \param[in] c ID of result column/dependent variable.
///
/// \param[in] x Points at which to evaluate interpolant.
///
/// \return Function values of dependent variable \p c evaluated at
/// points \p x in table \p t.
std::vector<double>
interpolate(const InTable& t,
const ResultColumn& c,
const std::vector<double>& x) const;
/// Retrieve connate saturation from all tables.
std::vector<double> connateSat() const;
/// Retrieve critical saturation for particular result column in all
/// tables.
std::vector<double> criticalSat(const ResultColumn& c) const;
/// Retrieve maximum saturation in all tables.
std::vector<double> maximumSat() const;
private:
/// Single tabulated 1D interpolant.
class SingleTable
{
public:
using ElmIt = ECLPropTableRawData::ElementIterator;
/// Constructor.
///
/// \param[in] xBegin Beginning (initial element) of linar range
/// of independent variable values.
///
/// \param[in] xEnd One past the end of linear range of
/// independent variable values.
///
/// \param[in,out] colIt Dependent/column range iterators. On
/// input, point to the beginnings of ranges of results
/// pertinent to a single table. On output, each iterator is
/// advanced across all rows of the SingleTable (including
/// sentinel/invalid nodes) which makes the pointers valid
/// for the next table if relevant (and called in a loop).
SingleTable(ElmIt xBegin,
ElmIt xEnd,
std::vector<ElmIt>& colIt);
/// Evaluate 1D interpolant in sequence of points.
///
/// \param[in] nCols Number of table columns.
///
/// \param[in] c ID of result column/dependent variable.
///
/// \param[in] x Points at which to evaluate interpolant.
///
/// \return Function values of dependent variable \p c evaluated
/// at points \p x.
std::vector<double>
interpolate(const ECLPropTableRawData::SizeType nCols,
const ResultColumn& c,
const std::vector<double>& x) const;
/// Retrieve connate saturation in table.
double connateSat() const;
/// Retrieve critical saturation for particular result column in
/// table.
double criticalSat(const ECLPropTableRawData::SizeType nCols,
const ResultColumn& c) const;
/// Retrieve maximum saturation in table.
double maximumSat() const;
private:
/// Independent variable.
std::vector<double> x_;
/// Dependent variable (or variables). Row major (i.e., C)
/// ordering. Number of elements: x_.size() * host.nCols_.
std::vector<double> y_;
/// Value of dependent variable at position (row,c).
double y(const ECLPropTableRawData::SizeType nCols,
const ECLPropTableRawData::SizeType row,
const ResultColumn& c) const;
};
/// Number of result/dependent variables (== #table cols - 1).
ECLPropTableRawData::SizeType nResCols_;
/// Sequence of individual tables, indexed by *NUM-type vectors.
std::vector<SingleTable> table_;
};
} // namespace Opm
#endif // OPM_ECLPROPTABLE_HEADER_INCLUDED

View File

@ -112,7 +112,9 @@ namespace {
result.reserve(x.size()); result.reserve(x.size());
for (const auto& xi : x) { for (const auto& xi : x) {
result.emplace_back(xi); // push_back(T(xi)) because vector<bool> does not
// support emplace_back until C++14.
result.push_back(Output(xi));
} }
return result; return result;
@ -188,6 +190,17 @@ namespace {
using type = void; using type = void;
}; };
/// Translate ERT type class to keyword element type.
///
/// Actual element type of \code ECL_INT_TYPE \endcode.
template <>
struct ElementType<ECL_BOOL_TYPE>
{
/// Element type of ERT Boolean (LOGICAL) data. Stored
/// internally as 'int'.
using type = int;
};
/// Translate ERT type class to keyword element type. /// Translate ERT type class to keyword element type.
/// ///
/// Actual element type of \code ECL_INT_TYPE \endcode. /// Actual element type of \code ECL_INT_TYPE \endcode.
@ -226,6 +239,37 @@ namespace {
template <ecl_type_enum Input> template <ecl_type_enum Input>
struct ExtractKeywordElements; struct ExtractKeywordElements;
/// Extract ERT keyword Boolean (LOGICAL) data.
template <>
struct ExtractKeywordElements<ECL_BOOL_TYPE>
{
using EType = ElementType<ECL_BOOL_TYPE>::type;
/// Function call operator.
///
/// Retrieve actual data elements from ERT keyword of integer
/// (specifically, \c int) type.
///
/// \param[in] kw ERT keyword instance.
///
/// \param[in,out] x Linearised keyword data elements. On
/// input points to memory block of size \code
/// ecl_kw_get_size(kw) * sizeof *x \endcode bytes. On
/// output, those bytes are filled with the actual data
/// values of \p kw.
void operator()(const ecl_kw_type* kw, EType* x) const
{
// 1) Extract raw 'int' values.
ecl_kw_get_memcpy_int_data(kw, x);
// 2) Convert to 'bool'-like values by comparing to
// magic constant ECL_BOOL_TRUE_INT (ecl_util.h).
for (auto n = ecl_kw_get_size(kw), i = 0*n; i < n; ++i) {
x[i] = static_cast<EType>(x[i] == ECL_BOOL_TRUE_INT);
}
}
};
/// Extract ERT keyword integer data. /// Extract ERT keyword integer data.
template <> template <>
struct ExtractKeywordElements<ECL_INT_TYPE> struct ExtractKeywordElements<ECL_INT_TYPE>
@ -489,6 +533,10 @@ namespace {
return GetKeywordData<ECL_CHAR_TYPE>:: return GetKeywordData<ECL_CHAR_TYPE>::
as<T>(kw, makeStringVector); as<T>(kw, makeStringVector);
case ECL_BOOL_TYPE:
return GetKeywordData<ECL_BOOL_TYPE>::
as<T>(kw, makeStringVector);
case ECL_INT_TYPE: case ECL_INT_TYPE:
return GetKeywordData<ECL_INT_TYPE>:: return GetKeywordData<ECL_INT_TYPE>::
as<T>(kw, makeStringVector); as<T>(kw, makeStringVector);
@ -1569,6 +1617,10 @@ namespace Opm {
ECLRestartData::keywordData<std::string>(const std::string& vector, ECLRestartData::keywordData<std::string>(const std::string& vector,
const std::string& gridID) const; const std::string& gridID) const;
template std::vector<bool>
ECLRestartData::keywordData<bool>(const std::string& vector,
const std::string& gridID) const;
template std::vector<int> template std::vector<int>
ECLRestartData::keywordData<int>(const std::string& vector, ECLRestartData::keywordData<int>(const std::string& vector,
const std::string& gridID) const; const std::string& gridID) const;
@ -1647,6 +1699,10 @@ namespace Opm {
ECLInitFileData::keywordData<std::string>(const std::string& vector, ECLInitFileData::keywordData<std::string>(const std::string& vector,
const std::string& gridID) const; const std::string& gridID) const;
template std::vector<bool>
ECLInitFileData::keywordData<bool>(const std::string& vector,
const std::string& gridID) const;
template std::vector<int> template std::vector<int>
ECLInitFileData::keywordData<int>(const std::string& vector, ECLInitFileData::keywordData<int>(const std::string& vector,
const std::string& gridID) const; const std::string& gridID) const;

View File

@ -0,0 +1,139 @@
/*
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ECLSATURATIONFUNC_HEADER_INCLUDED
#define OPM_ECLSATURATIONFUNC_HEADER_INCLUDED
#include <opm/utility/ECLPhaseIndex.hpp>
#include <memory>
#include <vector>
/// \file
///
/// Public interface to relative permeability evaluation machinery. The
/// back-end is aware of ECLIPSE's standard three-phase model for relative
/// permeability of oil and the two- and three-point saturation end-point
/// scaling options. Vertical scaling of relative permeability is not
/// supported at present.
namespace Opm {
class ECLGraph;
class ECLRestartData;
class ECLInitFileData;
/// Gateway to engine for computing relative permeability values based
/// on tabulated saturation functions in ECL output.
class ECLSaturationFunc
{
public:
/// Constructor
///
/// \param[in] G Connected topology of current model's active cells.
/// Needed to linearise region mapping (e.g., SATNUM) that is
/// distributed on local grids to all of the model's active cells
/// (\code member function G.rawLinearisedCellData() \endcode).
///
/// \param[in] init Container of tabulated saturation functions and
/// saturation table end points, if applicable, for all active
/// cells in the model \p G.
///
/// \param[in] useEPS Whether or not to include effects of
/// saturation end-point scaling. No effect if the INIT result
/// set does not actually include saturation end-point scaling
/// data. Otherwise, enables turning EPS off even if associate
/// data is present in the INIT result set.
///
/// Default value (\c true) means that effects of EPS are
/// included if requisite data is present in the INIT result.
ECLSaturationFunc(const ECLGraph& G,
const ECLInitFileData& init,
const bool useEPS = true);
/// Destructor.
~ECLSaturationFunc();
/// Move constructor.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing engine for saturation function
/// evaluation. Does not have a valid implementation when the
/// constructor completes.
ECLSaturationFunc(ECLSaturationFunc&& rhs);
/// Copy constructor.
///
/// \param[in] rhs Existing engine for saturation function
/// evaluation.
ECLSaturationFunc(const ECLSaturationFunc& rhs);
/// Move assignment operator.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing engine for saturation function
/// evaluation. Does not have a valid implementation when the
/// constructor completes.
///
/// \return \code *this \endcode.
ECLSaturationFunc& operator=(ECLSaturationFunc&& rhs);
/// Assignment operator.
///
/// \param[in] rhs Existing engine for saturation function
/// evaluation.
///
/// \return \code *this \endcode.
ECLSaturationFunc& operator=(const ECLSaturationFunc& rhs);
/// Compute relative permeability values in all active cells for a
/// single phase.
///
/// \param[in] G Connected topology of current model's active cells.
/// Needed to linearise phase saturations (e.g., SOIL) that are
/// distributed on local grids to all of the model's active cells
/// (\code member function G.rawLinearisedCellData() \endcode).
///
/// \param[in] rstrt ECLIPSE restart vectors. Result set view
/// assumed to be positioned at a particular report step of
/// interest.
///
/// \param[in] p Phase for which to compute relative permeability
/// values.
///
/// \return Derived relative permeability values of active phase \p
/// p for all active cells in model \p G. Empty if phase \p p is
/// not actually active in the current result set.
std::vector<double>
relperm(const ECLGraph& G,
const ECLRestartData& rstrt,
const ECLPhaseIndex p) const;
private:
/// Implementation backend.
class Impl;
/// Pointer to actual backend/engine object.
std::unique_ptr<Impl> pImpl_;
};
} // namespace Opm
#endif // OPM_ECLSATURATIONFUNC_HEADER_INCLUDED

View File

@ -554,6 +554,14 @@ namespace {
return errorAcceptable(E.absolute, tol.absolute) return errorAcceptable(E.absolute, tol.absolute)
&& errorAcceptable(E.relative, tol.relative); && errorAcceptable(E.relative, tol.relative);
} }
::Opm::ECLGraph
constructGraph(const example::FilePaths& pth)
{
const auto I = ::Opm::ECLInitFileData(pth.init);
return ::Opm::ECLGraph::load(pth.grid, I);
}
} // namespace Anonymous } // namespace Anonymous
int main(int argc, char* argv[]) int main(int argc, char* argv[])
@ -564,7 +572,7 @@ try {
const auto rstrt = ::Opm::ECLRestartData(pth.restart); const auto rstrt = ::Opm::ECLRestartData(pth.restart);
const auto steps = availableReportSteps(pth); const auto steps = availableReportSteps(pth);
const auto graph = example::initGraph(pth); const auto graph = constructGraph(pth);
auto all_ok = true; auto all_ok = true;
for (const auto& quant : testQuantities(prm)) { for (const auto& quant : testQuantities(prm)) {

View File

@ -206,13 +206,21 @@ namespace {
return ! ((pointMetric(diff) > tol.absolute) || return ! ((pointMetric(diff) > tol.absolute) ||
(pointMetric(rat) > tol.relative)); (pointMetric(rat) > tol.relative));
} }
::Opm::ECLGraph
constructGraph(const example::FilePaths& pth)
{
const auto I = ::Opm::ECLInitFileData(pth.init);
return ::Opm::ECLGraph::load(pth.grid, I);
}
} // namespace Anonymous } // namespace Anonymous
int main(int argc, char* argv[]) int main(int argc, char* argv[])
try { try {
const auto prm = example::initParam(argc, argv); const auto prm = example::initParam(argc, argv);
const auto pth = example::FilePaths(prm); const auto pth = example::FilePaths(prm);
const auto G = example::initGraph(pth); const auto G = constructGraph(pth);
const auto T = G.transmissibility(); const auto T = G.transmissibility();
const auto ok = transfieldAcceptable(prm, T); const auto ok = transfieldAcceptable(prm, T);

View File

@ -0,0 +1,469 @@
/*
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM 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.
OPM 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 for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H
#if HAVE_DYNAMIC_BOOST_TEST
#define BOOST_TEST_DYN_LINK
#endif
#define NVERBOSE
#define BOOST_TEST_MODULE TEST_ECLENDPOINTSCALING
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <boost/test/unit_test.hpp>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <opm/utility/ECLEndPointScaling.hpp>
#include <exception>
#include <stdexcept>
#include <vector>
namespace {
template <class Collection1, class Collection2>
void check_is_close(const Collection1& c1, const Collection2& c2)
{
BOOST_REQUIRE_EQUAL(c1.size(), c2.size());
if (! c1.empty()) {
auto i1 = c1.begin(), e1 = c1.end();
auto i2 = c2.begin();
for (; i1 != e1; ++i1, ++i2) {
BOOST_CHECK_CLOSE(*i1, *i2, 1.0e-10);
}
}
}
::Opm::SatFunc::EPSEvalInterface::SaturationPoints
associate(const std::vector<double>& s)
{
using SatAssoc = ::Opm::SatFunc::
EPSEvalInterface::SaturationAssoc;
auto sp = ::Opm::SatFunc::
EPSEvalInterface::SaturationPoints{};
sp.reserve(s.size());
for (const auto& si : s) {
sp.push_back(SatAssoc{ 0, si });
}
return sp;
}
} // Namespace Anonymous
// =====================================================================
// Two-point scaling
// ---------------------------------------------------------------------
BOOST_AUTO_TEST_SUITE (TwoPointScaling_FullRange)
BOOST_AUTO_TEST_CASE (NoScaling)
{
namespace SF = ::Opm::SatFunc;
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.0, 0.0, 1.0 };
const auto smin = std::vector<double>{ 0.0 };
const auto smax = std::vector<double>{ 1.0 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledConnate)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.2, 1.0] maps to [ 0.0, 1.0 ]
const auto smin = std::vector<double>{ 0.2 };
const auto smax = std::vector<double>{ 1.0 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.0, 0.0, 1.0 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
0,
0.25,
0.5,
0.75,
1.0,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledMax)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.0, 0.8] maps to [ 0.0, 1.0 ]
const auto smin = std::vector<double>{ 0.0 };
const auto smax = std::vector<double>{ 0.8 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.0, 0.0, 1.0 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
0.25,
0.5,
0.75,
1.0,
1.0,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledBoth)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.2, 0.8] maps to [ 0.0, 1.0 ]
const auto smin = std::vector<double>{ 0.2 };
const auto smax = std::vector<double>{ 0.8 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.0, 0.0, 1.0 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
0.0,
1.0 / 3.0,
2.0 / 3.0,
1.0,
1.0,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_SUITE_END ()
// =====================================================================
BOOST_AUTO_TEST_SUITE (TwoPointScaling_ReducedRange)
BOOST_AUTO_TEST_CASE (NoScaling)
{
namespace SF = ::Opm::SatFunc;
const auto smin = std::vector<double>{ 0.2 };
const auto smax = std::vector<double>{ 0.8 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.2, 0.0, 0.8 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.2,
0.2,
0.4,
0.6,
0.8,
0.8,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledConnate)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.0, 1.0] maps to [ 0.2, 0.8 ]
// s_eff = 0.6*s + 0.2
const auto smin = std::vector<double>{ 0.0 };
const auto smax = std::vector<double>{ 1.0 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.2, 0.0, 0.8 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.20,
0.32,
0.44,
0.56,
0.68,
0.80,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledMax)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.2, 0.8] maps to [ 0.0, 1.0 ]
// s_eff = max(0.75*s + 0.05, 0.2)
const auto smin = std::vector<double>{ 0.2 };
const auto smax = std::vector<double>{ 1.0 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.2, 0.0, 0.8 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.20,
0.20,
0.35,
0.50,
0.65,
0.80,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledBoth)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.2, 0.8] maps to [ 0.5, 0.7 ]
// s_eff = min(max(0.2, 3*s - 13/10), 0.8)
const auto smin = std::vector<double>{ 0.5 };
const auto smax = std::vector<double>{ 0.7 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.2, 0.0, 0.8 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.2,
0.2,
0.2,
0.5,
0.8,
0.8,
};
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_SUITE_END ()
// =====================================================================
// Three-point (alternative) scaling, applicable to relperm only.
// ---------------------------------------------------------------------
BOOST_AUTO_TEST_SUITE (ThreePointScaling_FullRange)
BOOST_AUTO_TEST_CASE (NoScaling)
{
namespace SF = ::Opm::SatFunc;
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.0, 0.2, 1.0 };
const auto smin = std::vector<double>{ 0.0 };
const auto sdisp = std::vector<double>{ 0.2 };
const auto smax = std::vector<double>{ 1.0 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto eps = SF::ThreePointScaling{ smin, sdisp, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_CASE (ScaledConnate)
{
namespace SF = ::Opm::SatFunc;
// Mobile Range: [0.4, 1.0] maps to [ 0.0, 1.0 ]
const auto smin = std::vector<double>{ 0.1 };
const auto sdisp = std::vector<double>{ 0.4 };
const auto smax = std::vector<double>{ 1.0 };
const auto tep = SF::EPSEvalInterface::
TableEndPoints { 0.0, 0.2, 1.0 };
const auto s = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
1.0 / 15,
0.2,
7.0 / 15,
11.0 / 15,
1.0,
};
const auto eps = SF::ThreePointScaling{ smin, sdisp, smax };
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
}
BOOST_AUTO_TEST_SUITE_END ()

View File

@ -69,11 +69,13 @@ namespace FlowDiagnostics
/// al. (SPE 146446), Shook and Mitchell (SPE 124625). /// al. (SPE 146446), Shook and Mitchell (SPE 124625).
Graph flowCapacityStorageCapacityCurve(const Toolbox::Forward& injector_solution, Graph flowCapacityStorageCapacityCurve(const Toolbox::Forward& injector_solution,
const Toolbox::Reverse& producer_solution, const Toolbox::Reverse& producer_solution,
const std::vector<double>& pv) const std::vector<double>& pv,
const double max_pv_fraction)
{ {
return flowCapacityStorageCapacityCurve(injector_solution.fd.timeOfFlight(), return flowCapacityStorageCapacityCurve(injector_solution.fd.timeOfFlight(),
producer_solution.fd.timeOfFlight(), producer_solution.fd.timeOfFlight(),
pv); pv,
max_pv_fraction);
} }
@ -88,20 +90,30 @@ namespace FlowDiagnostics
/// al. (SPE 146446), Shook and Mitchell (SPE 124625). /// al. (SPE 146446), Shook and Mitchell (SPE 124625).
Graph flowCapacityStorageCapacityCurve(const std::vector<double>& injector_tof, Graph flowCapacityStorageCapacityCurve(const std::vector<double>& injector_tof,
const std::vector<double>& producer_tof, const std::vector<double>& producer_tof,
const std::vector<double>& pv) const std::vector<double>& pv,
const double max_pv_fraction)
{ {
if (pv.size() != injector_tof.size() || pv.size() != producer_tof.size()) { if (pv.size() != injector_tof.size() || pv.size() != producer_tof.size()) {
throw std::runtime_error("flowCapacityStorageCapacityCurve(): " throw std::runtime_error("flowCapacityStorageCapacityCurve(): "
"Input solutions must have same size."); "Input solutions must have same size.");
} }
// Compute max pv cutoff.
const double total_pv = std::accumulate(pv.begin(), pv.end(), 0.0);
const double max_pv = max_pv_fraction * total_pv;
// Sort according to total travel time. // Sort according to total travel time.
const int n = pv.size(); const int n = pv.size();
typedef std::pair<double, double> D2; typedef std::pair<double, double> D2;
std::vector<D2> time_and_pv(n); std::vector<D2> time_and_pv(n);
for (int ii = 0; ii < n; ++ii) { for (int ii = 0; ii < n; ++ii) {
time_and_pv[ii].first = injector_tof[ii] + producer_tof[ii]; // Total travel time. if (pv[ii] > max_pv) {
time_and_pv[ii].second = pv[ii]; time_and_pv[ii].first = 1e100;
time_and_pv[ii].second = 0.0;
} else {
time_and_pv[ii].first = injector_tof[ii] + producer_tof[ii]; // Total travel time.
time_and_pv[ii].second = pv[ii];
}
} }
std::sort(time_and_pv.begin(), time_and_pv.end()); std::sort(time_and_pv.begin(), time_and_pv.end());

View File

@ -44,11 +44,19 @@ namespace FlowDiagnostics
/// coefficient. For a technical description see Shavali et /// coefficient. For a technical description see Shavali et
/// al. (SPE 146446), Shook and Mitchell (SPE 124625). /// al. (SPE 146446), Shook and Mitchell (SPE 124625).
/// ///
/// Single cells with a very large pore volume can be filtered out
/// before creating the curve. The 'max_pv_fraction' parameter
/// gives a fraction such that, if a cell's fraction of the total
/// pore volume is above this number, that cell will be
/// ignored. This can be used to disregard numerical aquifers for
/// example.
///
/// Returns F (flow capacity) as a function of Phi (storage capacity), /// Returns F (flow capacity) as a function of Phi (storage capacity),
/// that is for the returned Graph g, g.first is Phi and g.second is F. /// that is for the returned Graph g, g.first is Phi and g.second is F.
Graph flowCapacityStorageCapacityCurve(const Toolbox::Forward& injector_solution, Graph flowCapacityStorageCapacityCurve(const Toolbox::Forward& injector_solution,
const Toolbox::Reverse& producer_solution, const Toolbox::Reverse& producer_solution,
const std::vector<double>& pore_volume); const std::vector<double>& pore_volume,
const double max_pv_fraction = 1.0);
/// This overload gets the injector and producer time-of-flight /// This overload gets the injector and producer time-of-flight
/// directly instead of extracting it from the solution /// directly instead of extracting it from the solution
@ -56,7 +64,8 @@ namespace FlowDiagnostics
/// overload. /// overload.
Graph flowCapacityStorageCapacityCurve(const std::vector<double>& injector_tof, Graph flowCapacityStorageCapacityCurve(const std::vector<double>& injector_tof,
const std::vector<double>& producer_tof, const std::vector<double>& producer_tof,
const std::vector<double>& pore_volume); const std::vector<double>& pore_volume,
const double max_pv_fraction = 1.0);
/// The Lorenz coefficient from the F-Phi curve. /// The Lorenz coefficient from the F-Phi curve.
/// ///