#1988 Update flow diag libraries to handle multiple connections in same well, and have PVT Rel Perm support.

This commit is contained in:
Jacob Støren 2017-10-11 18:02:00 +02:00
parent 6c51938000
commit 4d097a3149
56 changed files with 11193 additions and 1066 deletions

View File

@ -110,7 +110,7 @@ namespace RigFlowDiagInterfaceTools {
{
auto satfunc = Opm::ECLSaturationFunc(G, init);
Opm::ECLFluxCalc calc(G, std::move(satfunc));
Opm::ECLFluxCalc calc(G, init, 9.80665, true);
auto getFlux = [&calc, &rstrt]
(const Opm::ECLPhaseIndex p)
@ -122,12 +122,13 @@ namespace RigFlowDiagInterfaceTools {
}
template <class WellFluxes>
Opm::FlowDiagnostics::CellSetValues
std::map<Opm::FlowDiagnostics::CellSetID, Opm::FlowDiagnostics::CellSetValues>
extractWellFlows(const Opm::ECLGraph& G,
const WellFluxes& well_fluxes)
const WellFluxes& well_fluxes)
{
Opm::FlowDiagnostics::CellSetValues inflow;
std::map<Opm::FlowDiagnostics::CellSetID, Opm::FlowDiagnostics::CellSetValues> well_flows;
for (const auto& well : well_fluxes) {
Opm::FlowDiagnostics::CellSetValues& inflow = well_flows[Opm::FlowDiagnostics::CellSetID(well.name)];
for (const auto& completion : well.completions) {
const auto& gridName = completion.gridName;
const auto& ijk = completion.ijk;
@ -143,7 +144,7 @@ namespace RigFlowDiagInterfaceTools {
}
}
return inflow;
return well_flows;
}
}

View File

@ -147,6 +147,63 @@ RigFlowDiagSolverInterface::~RigFlowDiagSolverInterface()
}
#if 0
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void removeCrossFlowCells(std::pair<const std::string, std::vector<int>> & tracerCellIdxsPair,
std::map<Opm::FlowDiagnostics::CellSetID, Opm::FlowDiagnostics::CellSetValues> & WellInFluxPrCell,
std::function<bool(double)> isFlowOkFunction)
{
std::string tracerName = tracerCellIdxsPair.first;
tracerName = RimFlowDiagSolution::removeCrossFlowEnding(QString::fromStdString(tracerName)).toStdString();
auto cellSetIdInFlowsPair = WellInFluxPrCell.find(Opm::FlowDiagnostics::CellSetID(tracerName));
CVF_TIGHT_ASSERT(cellSetIdInFlowsPair != WellInFluxPrCell.end());
std::vector<int> filteredCellIndices;
for ( int activeCellIdx : tracerCellIdxsPair.second )
{
auto activeCellIdxFluxPair = cellSetIdInFlowsPair->second.find(activeCellIdx);
CVF_TIGHT_ASSERT(activeCellIdxFluxPair != cellSetIdInFlowsPair->second.end());
if ( isFlowOkFunction(activeCellIdxFluxPair->second) )
{
filteredCellIndices.push_back(activeCellIdx);
}
}
if ( tracerCellIdxsPair.second.size() != filteredCellIndices.size() )
{
tracerCellIdxsPair.second = filteredCellIndices;
}
}
#endif
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::string removeCrossFlowEnding(std::string tracerName)
{
return RimFlowDiagSolution::removeCrossFlowEnding(QString::fromStdString(tracerName)).toStdString();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool hasCrossFlowEnding(std::string tracerName)
{
return RimFlowDiagSolution::hasCrossFlowEnding(QString::fromStdString(tracerName));
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::string addCrossFlowEnding(std::string tracerName)
{
return RimFlowDiagSolution::addCrossFlowEnding(QString::fromStdString(tracerName)).toStdString();
}
//--------------------------------------------------------------------------------------------------
///
@ -253,7 +310,7 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
// Set up flow Toolbox with timestep data
Opm::FlowDiagnostics::CellSetValues sumWellFluxPrCell;
std::map<Opm::FlowDiagnostics::CellSetID, Opm::FlowDiagnostics::CellSetValues> WellInFluxPrCell;
{
if (m_eclipseCase->eclipseCaseData()->results(RiaDefines::MATRIX_MODEL)->hasFlowDiagUsableFluxes())
@ -266,7 +323,10 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
else
{
Opm::ECLInitFileData init(getInitFileName());
Opm::FlowDiagnostics::ConnectionValues connectionVals = RigFlowDiagInterfaceTools::calculateFluxField((*m_opmFlowDiagStaticData->m_eclGraph), init, *currentRestartData, phaseSelection);
Opm::FlowDiagnostics::ConnectionValues connectionVals = RigFlowDiagInterfaceTools::calculateFluxField((*m_opmFlowDiagStaticData->m_eclGraph),
init,
*currentRestartData,
phaseSelection);
m_opmFlowDiagStaticData->m_fldToolbox->assignConnectionFlux(connectionVals);
}
@ -279,20 +339,20 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
const std::vector<Opm::ECLWellSolution::WellData> well_fluxes = wsol.solution(*currentRestartData, gridNames);
sumWellFluxPrCell = RigFlowDiagInterfaceTools::extractWellFlows(*(m_opmFlowDiagStaticData->m_eclGraph), well_fluxes);
WellInFluxPrCell = RigFlowDiagInterfaceTools::extractWellFlows(*(m_opmFlowDiagStaticData->m_eclGraph), well_fluxes);
m_opmFlowDiagStaticData->m_fldToolbox->assignInflowFlux(sumWellFluxPrCell);
m_opmFlowDiagStaticData->m_fldToolbox->assignInflowFlux(WellInFluxPrCell);
#if 0
// Start Hack: Filter connection cells with inconsistent well in flow direction (Hack, we should do something better)
for ( auto& tracerCellIdxsPair: injectorTracers )
{
std::vector<int> filteredCellIndices;
for (int activeCellIdx : tracerCellIdxsPair.second)
{
auto activeCellIdxFluxPair = sumWellFluxPrCell.find(activeCellIdx);
CVF_TIGHT_ASSERT(activeCellIdxFluxPair != sumWellFluxPrCell.end());
auto activeCellIdxFluxPair = WellInFluxPrCell.find(activeCellIdx);
CVF_TIGHT_ASSERT(activeCellIdxFluxPair != WellInFluxPrCell.end());
if (activeCellIdxFluxPair->second > 0 )
{
@ -309,8 +369,8 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
for (int activeCellIdx : tracerCellIdxsPair.second)
{
auto activeCellIdxFluxPair = sumWellFluxPrCell.find(activeCellIdx);
CVF_TIGHT_ASSERT(activeCellIdxFluxPair != sumWellFluxPrCell.end());
auto activeCellIdxFluxPair = WellInFluxPrCell.find(activeCellIdx);
CVF_TIGHT_ASSERT(activeCellIdxFluxPair != WellInFluxPrCell.end());
if (activeCellIdxFluxPair->second < 0 )
{
@ -319,8 +379,20 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
}
if (tracerCellIdxsPair.second.size() != filteredCellIndices.size()) tracerCellIdxsPair.second = filteredCellIndices;
}
// End Hack
// New Filtering Probably not neccesary
for ( auto& tracerCellIdxsPair: injectorTracers )
{
removeCrossFlowCells(tracerCellIdxsPair, WellInFluxPrCell, [](double inFlow){ return inFlow > 0;});
}
for ( auto& tracerCellIdxsPair: producerTracers )
{
removeCrossFlowCells(tracerCellIdxsPair, WellInFluxPrCell, [](double inFlow){ return inFlow < 0;});
}
#endif
}
progressInfo.incrementProgress();
@ -328,60 +400,82 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
{
// Injection Solution
std::set<std::string> injectorCrossFlowTracers;
std::vector<CellSet> injectorCellSets;
for ( const auto& tIt: injectorTracers )
{
injectorCellSets.push_back(CellSet(CellSetID(tIt.first), tIt.second));
}
std::unique_ptr<Toolbox::Forward> injectorSolution;
try
{
injectorSolution.reset(new Toolbox::Forward( m_opmFlowDiagStaticData->m_fldToolbox->computeInjectionDiagnostics(injectorCellSets)));
}
catch (const std::exception& e)
{
QMessageBox::critical(nullptr, "ResInsight", "Flow Diagnostics: " + QString(e.what()));
return result;
}
for ( const auto& tIt: injectorTracers )
{
std::string tracerName = tIt.first;
if (hasCrossFlowEnding(tracerName))
{
tracerName = removeCrossFlowEnding(tracerName);
injectorCrossFlowTracers.insert(tracerName);
}
injectorCellSets.push_back(CellSet(CellSetID(tracerName), tIt.second));
}
for ( const CellSetID& tracerId: injectorSolution->fd.startPoints() )
{
CellSetValues tofVals = injectorSolution->fd.timeOfFlight(tracerId);
result.setTracerTOF(tracerId.to_string(), phaseSelection, tofVals);
CellSetValues fracVals = injectorSolution->fd.concentration(tracerId);
result.setTracerFraction(tracerId.to_string(), phaseSelection, fracVals);
try
{
injectorSolution.reset(new Toolbox::Forward(m_opmFlowDiagStaticData->m_fldToolbox->computeInjectionDiagnostics(injectorCellSets)));
}
catch ( const std::exception& e )
{
QMessageBox::critical(nullptr, "ResInsight", "Flow Diagnostics: " + QString(e.what()));
return result;
}
for ( const CellSetID& tracerId: injectorSolution->fd.startPoints() )
{
std::string tracername = tracerId.to_string();
if (injectorCrossFlowTracers.count(tracername)) tracername = addCrossFlowEnding(tracername);
CellSetValues tofVals = injectorSolution->fd.timeOfFlight(tracerId);
result.setTracerTOF(tracername, phaseSelection, tofVals);
CellSetValues fracVals = injectorSolution->fd.concentration(tracerId);
result.setTracerFraction(tracername, phaseSelection, fracVals);
}
}
progressInfo.incrementProgress();
progressInfo.setProgressDescription("Producer Solution");
// Producer Solution
std::set<std::string> producerCrossFlowTracers;
std::vector<CellSet> prodjCellSets;
for ( const auto& tIt: producerTracers )
{
prodjCellSets.push_back(CellSet(CellSetID(tIt.first), tIt.second));
}
std::unique_ptr<Toolbox::Reverse> producerSolution;
try
{
producerSolution.reset(new Toolbox::Reverse(m_opmFlowDiagStaticData->m_fldToolbox->computeProductionDiagnostics(prodjCellSets)));
}
catch ( const std::exception& e )
{
QMessageBox::critical(nullptr, "ResInsight", "Flow Diagnostics: " + QString(e.what()));
return result;
}
for ( const auto& tIt: producerTracers )
{
std::string tracerName = tIt.first;
if (hasCrossFlowEnding(tracerName))
{
tracerName = removeCrossFlowEnding(tracerName);
producerCrossFlowTracers.insert(tracerName);
}
prodjCellSets.push_back(CellSet(CellSetID(tracerName), tIt.second));
}
for ( const CellSetID& tracerId: producerSolution->fd.startPoints() )
{
CellSetValues tofVals = producerSolution->fd.timeOfFlight(tracerId);
result.setTracerTOF(tracerId.to_string(), phaseSelection, tofVals);
CellSetValues fracVals = producerSolution->fd.concentration(tracerId);
result.setTracerFraction(tracerId.to_string(), phaseSelection, fracVals);
try
{
producerSolution.reset(new Toolbox::Reverse(m_opmFlowDiagStaticData->m_fldToolbox->computeProductionDiagnostics(prodjCellSets)));
}
catch ( const std::exception& e )
{
QMessageBox::critical(nullptr, "ResInsight", "Flow Diagnostics: " + QString(e.what()));
return result;
}
for ( const CellSetID& tracerId: producerSolution->fd.startPoints() )
{
std::string tracername = tracerId.to_string();
if (producerCrossFlowTracers.count(tracername)) tracername = addCrossFlowEnding(tracername);
CellSetValues tofVals = producerSolution->fd.timeOfFlight(tracerId);
result.setTracerTOF(tracername, phaseSelection, tofVals);
CellSetValues fracVals = producerSolution->fd.concentration(tracerId);
result.setTracerFraction(tracername, phaseSelection, fracVals);
}
}
progressInfo.incrementProgress();
@ -394,17 +488,37 @@ RigFlowDiagTimeStepResult RigFlowDiagSolverInterface::calculate(size_t timeStepI
{
const auto& prodCellSet = prodjCellSets[pIdx];
std::string prodTracerName = prodCellSet.id().to_string();
CellSetID prodID(prodTracerName);
std::string uiProducerTracerName = prodTracerName;
if (producerCrossFlowTracers.count(prodTracerName))
{
uiProducerTracerName = addCrossFlowEnding(prodTracerName);
}
for ( const auto& injCellSet : injectorCellSets )
{
std::string injTracerName = injCellSet.id().to_string();
CellSetID injID(injTracerName);
std::pair<double, double> fluxPair = injectorProducerPairFlux(*(injectorSolution.get()),
*(producerSolution.get()),
injCellSet,
prodCellSet,
sumWellFluxPrCell);
injID,
prodID,
WellInFluxPrCell);
std::string uiInjectorTracerName = injTracerName;
if (injectorCrossFlowTracers.count(injTracerName))
{
uiInjectorTracerName = addCrossFlowEnding(injTracerName);
}
#pragma omp critical
{
result.setInjProdWellPairFlux(injCellSet.id().to_string(),
prodCellSet.id().to_string(),
result.setInjProdWellPairFlux(uiInjectorTracerName,
uiProducerTracerName,
fluxPair);
}
}

View File

@ -11,10 +11,10 @@ set(NRLIB_GITHUB_SHA "ba35d4359882f1c6f5e9dc30eb95fe52af50fd6f")
set(ERT_GITHUB_SHA "2e36798b43daf18c112b91aa3febbf2fccd4a95f")
# https://github.com/OPM/opm-flowdiagnostics
set(OPM_FLOWDIAGNOSTICS_SHA "b6e59ddcd2feba450c8612a7402c9239e442c0d4")
set(OPM_FLOWDIAGNOSTICS_SHA "b0b8bbd32881fa100661cc8da5d4843794a4184a")
# https://github.com/OPM/opm-flowdiagnostics-applications
set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "c78f50897cea10ed56c7eadd1a1b23aa5ffbc56e")
set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "e6ae223fbeb0c9bd83c63a5fb6f57e4a3ad2ec18")
# 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

View File

@ -9,11 +9,13 @@ Macro (add_acceptance_test casename)
String (REGEX REPLACE "\\.[^.]*$" "" basename "${casename}")
# Note: non-default tolerances used for TOF tests (defaults too strict)
Add_Test (NAME ToF_accept_${casename}_all_steps
COMMAND runAcceptanceTest
"case=${OPM_DATA_ROOT}/flow_diagnostic_test/eclipse-simulation/${basename}"
"ref-dir=${OPM_DATA_ROOT}/flow_diagnostic_test/fd-ref-data/${basename}"
"atol=${abs_tol}" "rtol=${rel_tol}")
"atol=5e-6" "rtol=1e-13")
EndMacro (add_acceptance_test)

View File

@ -17,43 +17,40 @@
cmake_minimum_required (VERSION 2.8)
# additional search modules
set(OPM_COMMON_ROOT "" CACHE PATH "Root directory containing OPM related cmake modules")
option(SIBLING_SEARCH "Search for other modules in sibling directories?" ON)
if (NOT OPM_COMMON_ROOT)
find_package(opm-common QUIET)
# Mandatory call to project
project(opm-flowdiagnostics-applications CXX)
if(SIBLING_SEARCH AND NOT opm-common_DIR)
# guess the sibling dir
get_filename_component(_leaf_dir_name ${PROJECT_BINARY_DIR} NAME)
get_filename_component(_parent_full_dir ${PROJECT_BINARY_DIR} DIRECTORY)
get_filename_component(_parent_dir_name ${_parent_full_dir} NAME)
#Try if <module-name>/<build-dir> is used
get_filename_component(_modules_dir ${_parent_full_dir} DIRECTORY)
if(IS_DIRECTORY ${_modules_dir}/opm-common/${_leaf_dir_name})
set(opm-common_DIR ${_modules_dir}/opm-common/${_leaf_dir_name})
else()
string(REPLACE ${PROJECT_NAME} opm-common _opm_common_leaf ${_leaf_dir_name})
if(NOT _leaf_dir_name STREQUAL _opm_common_leaf
AND IS_DIRECTORY ${_parent_full_dir}/${_opm_common_leaf})
# We are using build directories named <prefix><module-name><postfix>
set(opm-common_DIR ${_parent_full_dir}/${_opm_common_leaf})
elseif(IS_DIRECTORY ${_parent_full_dir}/opm-common)
# All modules are in a common build dir
set(opm-common_DIR "${_parent_full_dir}/opm-common}")
endif()
endif()
endif()
if(opm-common_DIR AND NOT IS_DIRECTORY ${opm-common_DIR})
message(WARNING "Value ${opm-common_DIR} passed to variable"
" opm-common_DIR is not a directory")
endif()
if (opm-common_FOUND)
include(OpmInit)
else()
unset(opm-common_FOUND)
find_package(opm-common REQUIRED)
if (NOT OPM_COMMON_ROOT AND SIBLING_SEARCH)
set(OPM_COMMON_ROOT ${PROJECT_SOURCE_DIR}/../opm-common)
endif()
if (OPM_COMMON_ROOT)
list(APPEND CMAKE_MODULE_PATH "${OPM_COMMON_ROOT}/cmake/Modules")
include (OpmInit OPTIONAL RESULT_VARIABLE OPM_INIT)
set(OPM_MACROS_ROOT ${OPM_COMMON_ROOT})
endif()
if (NOT OPM_INIT)
message("" )
message(" /---------------------------------------------------------------------------------\\")
message(" | Could not locate the opm build macros. The opm build macros |")
message(" | are in a separate repository - instructions to proceed: |")
message(" | |")
message(" | 1. Clone the repository: git clone git@github.com:OPM/opm-common.git |")
message(" | |")
message(" | 2. Run cmake in the current project with -DOPM_COMMON_ROOT=<path>/opm-common |")
message(" | |")
message(" \\---------------------------------------------------------------------------------/")
message("" )
message(FATAL_ERROR "Could not find OPM Macros")
endif()
endif()
include(OpmInit)
# not the same location as most of the other projects; this hook overrides
macro (dir_hook)
@ -73,35 +70,6 @@ include (${project}-prereqs)
include (CMakeLists_files.cmake)
macro (config_hook)
if (NOT ERT_FOUND)
set (HAVE_ERT_ECL_TYPE_H 0)
else ()
# ERT_FOUND
cmake_push_check_state ()
set (CMAKE_REQUIRED_INCLUDES ${ERT_INCLUDE_DIR})
set (CMAKE_REQUIRED_LIBRARIES ${ERT_LIBRARIES})
check_cxx_source_compiles (
"
#include <iostream>
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_type.h>
int main ()
{
ecl_kw_type* kw = nullptr;
std::cout << ecl_type_get_type(ecl_kw_get_data_type(kw)) << std::endl;
}
"
HAVE_ERT_ECL_TYPE_H)
cmake_pop_check_state ()
endif ()
list (APPEND "${project}_CONFIG_VARS" HAVE_ERT_ECL_TYPE_H)
endmacro (config_hook)
macro (prereqs_hook)

View File

@ -21,12 +21,20 @@
# the library needs it.
list (APPEND MAIN_SOURCE_FILES
opm/utility/ECLCaseUtilities.cpp
opm/utility/ECLEndPointScaling.cpp
opm/utility/ECLFluxCalc.cpp
opm/utility/ECLGraph.cpp
opm/utility/ECLPropTable.cpp
opm/utility/ECLPvtCommon.cpp
opm/utility/ECLPvtCurveCollection.cpp
opm/utility/ECLPvtGas.cpp
opm/utility/ECLPvtOil.cpp
opm/utility/ECLPvtWater.cpp
opm/utility/ECLRegionMapping.cpp
opm/utility/ECLResultData.cpp
opm/utility/ECLSaturationFunc.cpp
opm/utility/ECLTableInterpolation1D.cpp
opm/utility/ECLUnitHandling.cpp
opm/utility/ECLWellSolution.cpp
)
@ -34,28 +42,42 @@ list (APPEND MAIN_SOURCE_FILES
list (APPEND TEST_SOURCE_FILES
tests/test_eclendpointscaling.cpp
tests/test_eclproptable.cpp
tests/test_eclpvtcommon.cpp
tests/test_eclregionmapping.cpp
tests/test_eclsimple1dinterpolant.cpp
tests/test_eclunithandling.cpp
)
list (APPEND EXAMPLE_SOURCE_FILES
examples/computeFlowStorageCurve.cpp
examples/computeLocalSolutions.cpp
examples/computePhaseFluxes.cpp
examples/computeToFandTracers.cpp
examples/computeTracers.cpp
examples/extractFromRestart.cpp
examples/extractPropCurves.cpp
tests/runAcceptanceTest.cpp
tests/runLinearisedCellDataTest.cpp
tests/runTransTest.cpp
)
list (APPEND PUBLIC_HEADER_FILES
opm/utility/ECLCaseUtilities.hpp
opm/utility/ECLEndPointScaling.hpp
opm/utility/ECLFluxCalc.hpp
opm/utility/ECLGraph.hpp
opm/utility/ECLPhaseIndex.hpp
opm/utility/ECLPiecewiseLinearInterpolant.hpp
opm/utility/ECLPropTable.hpp
opm/utility/ECLPvtCommon.hpp
opm/utility/ECLPvtCurveCollection.hpp
opm/utility/ECLPvtGas.hpp
opm/utility/ECLPvtOil.hpp
opm/utility/ECLPvtWater.hpp
opm/utility/ECLRegionMapping.hpp
opm/utility/ECLResultData.hpp
opm/utility/ECLSaturationFunc.hpp
opm/utility/ECLTableInterpolation1D.hpp
opm/utility/ECLUnitHandling.hpp
opm/utility/ECLWellSolution.hpp
)

View File

@ -25,49 +25,158 @@
#include "exampleSetup.hpp"
#include <opm/flowdiagnostics/CellSet.hpp>
#include <fstream>
#include <ios>
#include <type_traits>
namespace {
using StartSets = std::vector< ::Opm::FlowDiagnostics::CellSet>;
/// Return completion cells of single well that both satisfy the
/// select_completion criterion and are active cells.
template <class WellData, class SelectCompletion>
std::vector<int>
activeCompletions(const Opm::ECLGraph& graph,
const WellData& well,
SelectCompletion&& select_completion)
{
std::vector<int> completion_cells;
completion_cells.reserve(well.completions.size());
for (const auto& completion : well.completions) {
if (select_completion(completion)) {
const int cell_index = graph.activeCell(completion.ijk, completion.gridName);
if (cell_index >= 0) {
completion_cells.push_back(cell_index);
}
}
}
return completion_cells;
}
/// Return (active-cell) completions from all wells that satisfy
/// the select_completion criterion, grouped by well.
template <class SelectCompletion>
StartSets
getCompletionsFromWells(const example::Setup& setup,
SelectCompletion&& select_completion)
{
auto start = StartSets{};
for (const auto& well : setup.well_fluxes) {
std::vector<int> completion_cells = activeCompletions(setup.graph, well, select_completion);
if (!completion_cells.empty()) {
start.emplace_back(Opm::FlowDiagnostics::CellSetID(well.name),
completion_cells);
}
}
return start;
}
StartSets injectors(const example::Setup& setup)
{
using Completion = Opm::ECLWellSolution::WellData::Completion;
return getCompletionsFromWells(setup, [](const Completion& completion)
{ return completion.reservoir_inflow_rate > 0.0; });
}
StartSets producers(const example::Setup& setup)
{
using Completion = Opm::ECLWellSolution::WellData::Completion;
return getCompletionsFromWells(setup, [](const Completion& completion)
{ return completion.reservoir_inflow_rate < 0.0; });
}
void printSolution(const ::Opm::FlowDiagnostics::CellSetValues& x,
const ::Opm::FlowDiagnostics::CellSetID& id,
const std::string& varname)
{
const auto filename =
varname + '-' + id.to_string() + ".out";
std::ofstream os(filename);
if (os) {
os.precision(16);
os.setf(std::ios_base::scientific);
for (const auto& item : x) {
os << item.first << ' ' << item.second << '\n';
}
}
}
std::vector<int>
extractCellIDs(const ::Opm::FlowDiagnostics::CellSetValues& x)
{
auto i = std::vector<int>{};
i.reserve(x.size());
for (const auto& xi : x) { i.push_back(xi.first); }
std::sort(std::begin(i), std::end(i));
return i;
}
bool
sameReachability(const ::Opm::FlowDiagnostics::CellSetValues& tof,
const ::Opm::FlowDiagnostics::CellSetValues& conc)
{
if (tof.size() != conc.size()) {
return false;
}
const auto tof_id = extractCellIDs(tof);
const auto conc_id = extractCellIDs(conc);
return tof_id == conc_id;
}
void runAnalysis(const StartSets& start,
const ::Opm::FlowDiagnostics::Solution& sol,
const bool is_inj)
{
auto ok = std::vector<bool>{};
ok.reserve(start.size());
for (const auto& pt : start) {
const auto& id = pt.id();
const auto& tof = sol.timeOfFlight (id);
const auto& conc = sol.concentration(id);
const std::string injprod = is_inj ? "inj" : "prod";
printSolution(tof , id, "tof-" + injprod);
printSolution(conc, id, "conc-" + injprod);
if (! sameReachability(tof, conc)) {
std::cout << id.to_string() << ": FAIL\n";
}
}
}
}
// Syntax (typical):
// computeToFandTracers case=<ecl_case_prefix> step=<report_number>
// computeLocalSolutions case=<ecl_case_prefix> step=<report_number>
int main(int argc, char* argv[])
try {
example::Setup setup(argc, argv);
auto& fdTool = setup.toolbox;
// Create start sets from injector wells.
using ID = Opm::FlowDiagnostics::CellSetID;
std::vector<Opm::FlowDiagnostics::CellSet> start;
for (const auto& well : setup.well_fluxes) {
if (!well.is_injector_well) {
continue;
}
std::vector<int> completion_cells;
completion_cells.reserve(well.completions.size());
for (const auto& completion : well.completions) {
const auto& gridName = completion.gridName;
const auto& ijk = completion.ijk;
const int cell_index = setup.graph.activeCell(ijk, gridName);
if (cell_index >= 0) {
completion_cells.push_back(cell_index);
}
}
start.emplace_back(ID(well.name), completion_cells);
{
const auto inj = injectors(setup);
const auto fwd = fdTool.computeInjectionDiagnostics(inj);
runAnalysis(inj, fwd.fd, true);
}
{
const auto prod = producers(setup);
const auto rev = fdTool.computeProductionDiagnostics(prod);
// Solve for injection time of flight and tracers.
auto sol = fdTool.computeInjectionDiagnostics(start);
// Choose injector id, default to first injector.
const std::string id_string = setup.param.getDefault("id", start.front().id().to_string());
const ID id(id_string);
// Get local data for injector.
const bool tracer = setup.param.getDefault("tracer", false);
const auto& data = tracer ? sol.fd.concentration(id) : sol.fd.timeOfFlight(id);
// Write it to standard out.
std::cout.precision(16);
for (auto item : data) {
std::cout << item.first << " " << item.second << '\n';
runAnalysis(prod, rev.fd, false);
}
}
catch (const std::exception& e) {

View File

@ -0,0 +1,229 @@
/*
Copyright 2017 SINTEF ICT, Applied Mathematics.
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 <examples/exampleSetup.hpp>
#include <opm/utility/ECLCaseUtilities.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <chrono>
#include <exception>
#include <ios>
#include <iostream>
#include <iomanip>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/system/error_code.hpp>
namespace {
template <class OStream, class Task>
void timeIt(OStream& os, Task&& task)
{
const auto start = ::std::chrono::steady_clock::now();
task();
const auto stop = ::std::chrono::steady_clock::now();
os << std::setprecision(3) << std::scientific
<< std::chrono::duration<double>(stop - start).count()
<< " [s]" << std::endl;
}
std::string phaseName(const Opm::ECLPhaseIndex p)
{
switch (p) {
case Opm::ECLPhaseIndex::Aqua: return "water";
case Opm::ECLPhaseIndex::Liquid: return "oil";
case Opm::ECLPhaseIndex::Vapour: return "gas";
}
throw std::invalid_argument {
"Invalid Phase ID"
};
}
int numDigits(const std::vector<int>& steps)
{
if (steps.empty()) {
return 1;
}
const auto m =
*std::max_element(std::begin(steps), std::end(steps));
if (m == 0) {
return 1;
}
assert (m > 0);
return std::floor(std::log10(static_cast<double>(m))) + 1;
}
void openRestartSet(const Opm::ECLCaseUtilities::ResultSet& rset,
const int step,
std::unique_ptr<Opm::ECLRestartData>& rstrt)
{
if (! (rset.isUnifiedRestart() && rstrt)) {
// Not a unified restart file or this is the first time we're
// seeing the result set.
rstrt.reset(new Opm::ECLRestartData(rset.restartFile(step)));
}
}
void savePhaseVector(const std::string& quant,
const std::string& phase,
const std::string& type,
const int step,
const int ndgt,
const std::vector<double>& pflux)
{
namespace fs = boost::filesystem;
const auto dir = fs::path{ quant } / phase;
boost::system::error_code ec{};
if (fs::create_directories(dir, ec) ||
(ec.value() == boost::system::errc::errc_t::success))
{
auto fn = dir;
{
std::ostringstream os;
os << type << '-'
<< std::setw(ndgt) << std::setfill('0')
<< step << ".txt";
fn /= os.str();
}
fs::ofstream os(fn);
if (os) {
os.precision(16);
os.setf(std::ios::scientific);
for (const auto& qi : pflux) {
os << qi << '\n';
}
}
}
}
void saveNeighbours(const ::Opm::ECLGraph& G)
{
std::ofstream os("neigh.txt");
if (!os) { return; }
const auto& neigh = G.neighbours();
for (auto nconn = neigh.size() / 2,
conn = 0*nconn; conn < nconn; ++conn)
{
os << neigh[2*conn + 0 ] << ' ' << neigh[2*conn + 1] << '\n';
}
}
void computePhaseFluxes(const Opm::ECLGraph& G,
const Opm::ECLCaseUtilities::ResultSet& rset,
const Opm::ECLFluxCalc& fcalc,
const int step,
const int ndgt,
std::unique_ptr<Opm::ECLRestartData>& rstrt)
{
openRestartSet(rset, step, rstrt);
if (! (rstrt && rstrt->selectReportStep(step))) {
std::cout << " Failed (No Such Report Step)\n";
}
for (const auto& phase : G.activePhases()) {
const auto pname = phaseName(phase);
auto pflux = std::vector<double>{};
timeIt(std::cout, [&fcalc, &rstrt, phase, &pname, &pflux]()
{
pflux = fcalc.flux(*rstrt, phase);
std::cout << " - " << std::right
<< std::setw(5) << std::setfill(' ')
<< pname << ": ";
});
if (! pflux.empty()) {
savePhaseVector("flux", pname, "calc", step, ndgt, pflux);
}
// Extract reference fluxes if available.
pflux = G.flux(*rstrt, phase);
if (! pflux.empty()) {
savePhaseVector("flux", pname, "ref", step, ndgt, pflux);
}
}
}
} // namespace Anonymous
int main(int argc, char* argv[])
try {
const auto prm = example::initParam(argc, argv);
const auto grav = prm.getDefault("grav" , 9.80665);
const auto useEPS = prm.getDefault("useEPS", false);
const auto rset = example::identifyResultSet(prm);
const auto steps = rset.reportStepIDs();
const auto ndgt = numDigits(steps);
const auto init = Opm::ECLInitFileData(rset.initFile());
const auto graph = Opm::ECLGraph::load(rset.gridFile(), init);
const auto fcalc = Opm::ECLFluxCalc(graph, init, grav, useEPS);
if (prm.getDefault("emitNeigh", false)) {
saveNeighbours(graph);
}
auto rstrt = std::unique_ptr<Opm::ECLRestartData>{};
for (const auto& step : steps) {
timeIt(std::cout, [&rset, &graph, &rstrt, &fcalc, ndgt, step]()
{
std::cout << "Phase Fluxes for Report Step "
<< std::setw(ndgt) << std::setfill('0')
<< step << std::endl;
computePhaseFluxes(graph, rset, fcalc, step, ndgt, rstrt);
std::cout << " - Step Time: ";
});
std::cout << std::endl;
}
}
catch (const std::exception& e) {
std::cerr << "Caught Exception: " << e.what() << '\n';
return EXIT_FAILURE;
}

View File

@ -29,6 +29,7 @@
#include <opm/flowdiagnostics/ConnectionValues.hpp>
#include <opm/flowdiagnostics/Toolbox.hpp>
#include <opm/utility/ECLCaseUtilities.hpp>
#include <opm/utility/ECLFluxCalc.hpp>
#include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
@ -36,52 +37,16 @@
#include <opm/utility/ECLWellSolution.hpp>
#include <exception>
#include <initializer_list>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
namespace example {
inline bool isFile(const boost::filesystem::path& p)
{
namespace fs = boost::filesystem;
auto is_regular_file = [](const fs::path& pth)
{
return fs::exists(pth) && fs::is_regular_file(pth);
};
return is_regular_file(p)
|| (fs::is_symlink(p) &&
is_regular_file(fs::read_symlink(p)));
}
inline boost::filesystem::path
deriveFileName(boost::filesystem::path file,
const std::vector<std::string>& extensions)
{
for (const auto& ext : extensions) {
file.replace_extension(ext);
if (isFile(file)) {
return file;
}
}
const auto prefix = file.parent_path() / file.stem();
std::ostringstream os;
os << "Unable to derive valid filename from model prefix "
<< prefix.generic_string();
throw std::invalid_argument(os.str());
}
template <class FluxCalc>
inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G,
@ -123,35 +88,32 @@ namespace example {
const bool useEPS)
{
if (compute_fluxes) {
auto satfunc = ::Opm::ECLSaturationFunc(G, init, useEPS);
const auto grav = 0.0;
Opm::ECLFluxCalc calc(G, std::move(satfunc));
Opm::ECLFluxCalc calc(G, init, grav, useEPS);
auto getFlux = [&calc, &rstrt]
(const Opm::ECLPhaseIndex p)
return extractFluxField(G, [&calc, &rstrt]
(const Opm::ECLPhaseIndex p)
{
return calc.flux(rstrt, p);
};
return extractFluxField(G, getFlux);
});
}
auto getFlux = [&G, &rstrt]
(const Opm::ECLPhaseIndex p)
return extractFluxField(G, [&G, &rstrt]
(const Opm::ECLPhaseIndex p)
{
return G.flux(rstrt, p);
};
return extractFluxField(G, getFlux);
});
}
template <class WellFluxes>
Opm::FlowDiagnostics::CellSetValues
std::map<Opm::FlowDiagnostics::CellSetID, Opm::FlowDiagnostics::CellSetValues>
extractWellFlows(const Opm::ECLGraph& G,
const WellFluxes& well_fluxes)
{
Opm::FlowDiagnostics::CellSetValues inflow;
std::map<Opm::FlowDiagnostics::CellSetID, Opm::FlowDiagnostics::CellSetValues> well_flows;
for (const auto& well : well_fluxes) {
Opm::FlowDiagnostics::CellSetValues& inflow = well_flows[Opm::FlowDiagnostics::CellSetID(well.name)];
for (const auto& completion : well.completions) {
const auto& gridName = completion.gridName;
const auto& ijk = completion.ijk;
@ -167,32 +129,40 @@ namespace example {
}
}
return inflow;
return well_flows;
}
struct FilePaths
inline Opm::ECLCaseUtilities::ResultSet
identifyResultSet(const Opm::ParameterGroup& param)
{
FilePaths(const Opm::ParameterGroup& param)
for (const auto* p : { "case", "grid", "init", "restart" })
{
const string casename = param.getDefault<string>("case", "DEFAULT_CASE_NAME");
grid = param.has("grid") ? param.get<string>("grid")
: deriveFileName(casename, { ".EGRID", ".FEGRID", ".GRID", ".FGRID" });
init = param.has("init") ? param.get<string>("init")
: deriveFileName(casename, { ".INIT", ".FINIT" });
restart = param.has("restart") ? param.get<string>("restart")
: deriveFileName(casename, { ".UNRST", ".FUNRST" });
if (param.has(p)) {
return Opm::ECLCaseUtilities::ResultSet {
param.get<std::string>(p)
};
}
}
using path = boost::filesystem::path;
using string = std::string;
throw std::invalid_argument {
"No Valid Result Set Identified by Input Parameters"
};
}
inline std::unordered_set<int>
getAvailableSteps(const ::Opm::ECLCaseUtilities::ResultSet& result_set)
{
const auto steps = result_set.reportStepIDs();
return { std::begin(steps), std::end(steps) };
}
path grid;
path init;
path restart;
};
@ -231,10 +201,10 @@ namespace example {
{
Setup(int argc, char** argv)
: param (initParam(argc, argv))
, file_paths (param)
, init (file_paths.init)
, rstrt (file_paths.restart)
, graph (::Opm::ECLGraph::load(file_paths.grid, init))
, result_set (identifyResultSet(param))
, init (result_set.initFile())
, graph (::Opm::ECLGraph::load(result_set.gridFile(), init))
, available_steps(getAvailableSteps(result_set))
, well_fluxes ()
, toolbox (initToolbox(graph))
, compute_fluxes_(param.getDefault("compute_fluxes", false))
@ -247,7 +217,7 @@ namespace example {
os << "Report Step " << step
<< " is Not Available in Result Set "
<< file_paths.grid.stem();
<< this->result_set.gridFile().stem();
throw std::domain_error(os.str());
}
@ -255,16 +225,33 @@ namespace example {
bool selectReportStep(const int step)
{
if (! rstrt.selectReportStep(step)) {
if (this->available_steps.count(step) == 0) {
// Requested report step not amongst those stored in the
// result set.
return false;
}
if (! (this->result_set.isUnifiedRestart() &&
bool(this->restart)))
{
// Non-unified (separate) restart files, or first time-step
// selection in a unified restart file case.
const auto restart_file =
this->result_set.restartFile(step);
this->openRestartFile(restart_file);
}
if (! this->restart->selectReportStep(step)) {
return false;
}
{
auto wsol = Opm::ECLWellSolution{};
well_fluxes = wsol.solution(rstrt, graph.activeGrids());
well_fluxes = wsol.solution(*restart, graph.activeGrids());
}
toolbox.assignConnectionFlux(extractFluxField(graph, init, rstrt,
toolbox.assignConnectionFlux(extractFluxField(graph, init, *restart,
compute_fluxes_, useEPS_));
toolbox.assignInflowFlux(extractWellFlows(graph, well_fluxes));
@ -273,14 +260,22 @@ namespace example {
}
Opm::ParameterGroup param;
FilePaths file_paths;
Opm::ECLCaseUtilities::ResultSet result_set;
Opm::ECLInitFileData init;
Opm::ECLRestartData rstrt;
Opm::ECLGraph graph;
std::unordered_set<int> available_steps;
std::vector<Opm::ECLWellSolution::WellData> well_fluxes;
Opm::FlowDiagnostics::Toolbox toolbox;
bool compute_fluxes_ = false;
bool useEPS_ = false;
std::shared_ptr<Opm::ECLRestartData> restart;
private:
void openRestartFile(const boost::filesystem::path& rstrt)
{
this->restart.reset(new Opm::ECLRestartData{ rstrt });
}
};

View File

@ -0,0 +1,233 @@
/*
Copyright 2017 SINTEF ICT, Applied Mathematics.
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 <examples/exampleSetup.hpp>
#include <opm/utility/ECLCaseUtilities.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <opm/utility/ECLPvtCurveCollection.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLSaturationFunc.hpp>
#include <cstddef>
#include <exception>
#include <iomanip>
#include <ios>
#include <vector>
#include <boost/filesystem.hpp>
namespace {
template <class OStream>
void printGraph(OStream& os,
const std::string& name,
const Opm::FlowDiagnostics::Graph& graph)
{
const auto& x = graph.first;
const auto& y = graph.second;
const auto oprec = os.precision(16);
const auto oflags = os.setf(std::ios_base::scientific);
os << name << " = [\n";
for (auto n = x.size(), i = 0*n; i < n; ++i) {
os << x[i] << ' ' << y[i] << '\n';
}
os << "];\n\n";
os.setf(oflags);
os.precision(oprec);
}
// -----------------------------------------------------------------
// Relative permeability
void krg(const Opm::ECLSaturationFunc& sfunc,
const int activeCell,
const bool useEPS)
{
using RC = Opm::ECLSaturationFunc::RawCurve;
auto func = std::vector<RC>{};
func.reserve(1);
// Request krg (gas rel-perm in oil-gas system)
func.push_back(RC{
RC::Function::RelPerm,
RC::SubSystem::OilGas,
Opm::ECLPhaseIndex::Vapour
});
const auto graph =
sfunc.getSatFuncCurve(func, activeCell, useEPS);
printGraph(std::cout, "krg", graph[0]);
}
void krog(const Opm::ECLSaturationFunc& sfunc,
const int activeCell,
const bool useEPS)
{
using RC = Opm::ECLSaturationFunc::RawCurve;
auto func = std::vector<RC>{};
func.reserve(1);
// Request krog (oil rel-perm in oil-gas system)
func.push_back(RC{
RC::Function::RelPerm,
RC::SubSystem::OilGas,
Opm::ECLPhaseIndex::Liquid
});
const auto graph =
sfunc.getSatFuncCurve(func, activeCell, useEPS);
printGraph(std::cout, "krog", graph[0]);
}
void krow(const Opm::ECLSaturationFunc& sfunc,
const int activeCell,
const bool useEPS)
{
using RC = Opm::ECLSaturationFunc::RawCurve;
auto func = std::vector<RC>{};
func.reserve(1);
// Request krow (oil rel-perm in oil-water system)
func.push_back(RC{
RC::Function::RelPerm,
RC::SubSystem::OilWater,
Opm::ECLPhaseIndex::Liquid
});
const auto graph =
sfunc.getSatFuncCurve(func, activeCell, useEPS);
printGraph(std::cout, "krow", graph[0]);
}
void krw(const Opm::ECLSaturationFunc& sfunc,
const int activeCell,
const bool useEPS)
{
using RC = Opm::ECLSaturationFunc::RawCurve;
auto func = std::vector<RC>{};
func.reserve(1);
// Request krw (water rel-perm in oil-water system)
func.push_back(RC{
RC::Function::RelPerm,
RC::SubSystem::OilWater,
Opm::ECLPhaseIndex::Aqua
});
const auto graph =
sfunc.getSatFuncCurve(func, activeCell, useEPS);
printGraph(std::cout, "krw", graph[0]);
}
// -----------------------------------------------------------------
// PVT Curves
void Bg(const Opm::ECLPVT::ECLPvtCurveCollection& pvtCurves,
const int activeCell)
{
using RC = Opm::ECLPVT::RawCurve;
const auto graph = pvtCurves
.getPvtCurve(RC::FVF, Opm::ECLPhaseIndex::Vapour, activeCell);
printGraph(std::cout, "Bg", graph);
}
void mu_g(const Opm::ECLPVT::ECLPvtCurveCollection& pvtCurves,
const int activeCell)
{
using RC = Opm::ECLPVT::RawCurve;
const auto graph = pvtCurves
.getPvtCurve(RC::Viscosity, Opm::ECLPhaseIndex::Vapour, activeCell);
printGraph(std::cout, "mu_g", graph);
}
void Bo(const Opm::ECLPVT::ECLPvtCurveCollection& pvtCurves,
const int activeCell)
{
using RC = Opm::ECLPVT::RawCurve;
const auto graph = pvtCurves
.getPvtCurve(RC::FVF, Opm::ECLPhaseIndex::Liquid, activeCell);
printGraph(std::cout, "Bo", graph);
}
void mu_o(const Opm::ECLPVT::ECLPvtCurveCollection& pvtCurves,
const int activeCell)
{
using RC = Opm::ECLPVT::RawCurve;
const auto graph = pvtCurves
.getPvtCurve(RC::Viscosity, Opm::ECLPhaseIndex::Liquid, activeCell);
printGraph(std::cout, "mu_o", graph);
}
} // namespace Anonymous
int main(int argc, char* argv[])
try {
const auto prm = example::initParam(argc, argv);
const auto useEPS = prm.getDefault("useEPS", false);
const auto cellID = prm.getDefault("cell", 0);
const auto rset = example::identifyResultSet(prm);
const auto init = Opm::ECLInitFileData(rset.initFile());
const auto graph = Opm::ECLGraph::load(rset.gridFile(), init);
const auto sfunc = Opm::ECLSaturationFunc(graph, init, useEPS);
const auto pvtCC = Opm::ECLPVT::ECLPvtCurveCollection(graph, init);
// -----------------------------------------------------------------
// Relative permeability
if (prm.getDefault("krg" , false)) { krg (sfunc, cellID, useEPS); }
if (prm.getDefault("krog", false)) { krog(sfunc, cellID, useEPS); }
if (prm.getDefault("krow", false)) { krow(sfunc, cellID, useEPS); }
if (prm.getDefault("krw" , false)) { krw (sfunc, cellID, useEPS); }
// -----------------------------------------------------------------
// PVT Curves
if (prm.getDefault("Bg" , false)) { Bg (pvtCC, cellID); }
if (prm.getDefault("mu_g", false)) { mu_g(pvtCC, cellID); }
if (prm.getDefault("Bo" , false)) { Bo (pvtCC, cellID); }
if (prm.getDefault("mu_o", false)) { mu_o(pvtCC, cellID); }
}
catch (const std::exception& e) {
std::cerr << "Caught Exception: " << e.what() << '\n';
return EXIT_FAILURE;
}

View File

@ -0,0 +1,326 @@
#include <opm/utility/ECLCaseUtilities.hpp>
#include <exception>
#include <initializer_list>
#include <iomanip>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_file_kw.h>
#include <ert/ecl/ecl_file_view.h>
#include <ert/util/ert_unique_ptr.hpp>
/// Function object that matches strings with a common prefix (i.e., a file
/// name base) followed by .F or .X and then exactly four digits and nothing
/// further.
///
/// In other words, if prefix is "NORNE_ATW2013", then the function will
/// return true for strings of the form
///
/// -* NORNE_ATW2013.F0000
/// -* NORNE_ATW2013.X1234
///
/// but it will return false for anything else like strings of the form
///
/// -* NORNE_ATW2013.S0123
/// -* NORNE_ATW2013.FUNRST
/// -* NORNE_ATW2013.X12345
/// -* NORNE_ATW2013.F012
/// -* NORNE_ATW2013.X012Y
/// -* NORNE_ATW2013.X0123.4
/// -* NORNE_ATW2014.F0001
///
class IsSeparateRestart
{
public:
/// Constructor.
///
/// \param[in] prefix Common string prefix (file name base).
IsSeparateRestart(const std::string& prefix)
: restart_(prefix + R"~~(\.(F|X)\d{4}$)~~")
{
// Common prefix + '.' + (F or X) + four digits + "end of string".
}
/// Match string against stored regular expression engine.
///
/// \param[in] e Name, possibly full path, of file system element.
/// Typically names a regular file.
///
/// \return True if the element matches the entire stored regular
/// expression (\code regex_match() \endcode rather than \code
/// regex_search() \endcode), false otherwise.
bool operator()(const boost::filesystem::path& e) const
{
return std::regex_match(e.filename().generic_string(),
this->restart_);
}
private:
/// Regular expression against which to match filesystem elements.
std::regex restart_;
};
namespace {
template <class String>
boost::filesystem::path
operator+(boost::filesystem::path p, const String& e)
{
return p += e;
}
bool isFile(const boost::filesystem::path& p)
{
namespace fs = boost::filesystem;
auto is_regular_file = [](const fs::path& pth)
{
return fs::exists(pth) && fs::is_regular_file(pth);
};
return is_regular_file(p)
|| (fs::is_symlink(p) &&
is_regular_file(fs::read_symlink(p)));
}
boost::filesystem::path
deriveFileName(boost::filesystem::path file,
std::initializer_list<const char*> ext)
{
for (const auto& e : ext) {
file.replace_extension(e);
if (isFile(file)) {
return file;
}
}
return "";
}
// Handle the possibility that a case prefix ends in a substring that
// looks like a file extension (e.g., "path/to/CASE.01"). This
// substring would be stripped off during deriveFileName() processing.
boost::filesystem::path
safeCasePrefix(const boost::filesystem::path& casename)
{
auto casefile = boost::filesystem::path{};
for (const auto* ignore : { "", ".@@@-HACK-@@@" }) {
casefile = deriveFileName(casename + ignore,
{ ".DATA" ,
".EGRID", ".FEGRID" ,
".GRID" , ".FGRID" });
if (! casefile.empty()) {
// Valid case file found.
break;
}
}
if (casefile.empty()) {
return casefile;
}
return casefile.parent_path() / (casefile.stem() + ".Imp-Detail-Hack");
}
// Look for separate restart files (.X000n or .F000n) that match the
// case-name prefix in the directory implied by prefix. Pick the most
// recent one (as defined by the element's write/modification time).
//
// Return empty if there are no separate restart files (that match the
// prefix).
boost::filesystem::path
mostRecentSeparateRestart(const boost::filesystem::path& prefix)
{
namespace fs = boost::filesystem;
const auto isSepRstrt = IsSeparateRestart {
prefix.stem().generic_string()
};
auto most_recent = fs::path{};
const fs::path parent_path = prefix.parent_path() == "" ? "." : prefix.parent_path();
auto max_mtime = 0 * fs::last_write_time(parent_path);
for (auto i = fs::directory_iterator(parent_path),
e = fs::directory_iterator();
i != e; ++i)
{
if (! fs::is_regular_file(i->status())) {
// Not a file. Ignore.
continue;
}
const auto& elem = i->path();
if (! isSepRstrt(elem)) {
// Not a (separate) restart file. Ignore.
continue;
}
const auto mtime = fs::last_write_time(elem);
if (mtime > max_mtime) {
max_mtime = mtime;
most_recent = elem;
}
}
return most_recent;
}
// Determine whether or not result set uses a unified restart file.
//
// Steps:
// 1) Return false if no unified restart file matches prefix.
//
// 2) Otherwise, return true if no *separate* restart file matches
// prefix.
//
// 3) Otherwise, return true if unified restart file is more recent
// (in terms of modification/write time) than most recent separate
// restart file that matches prefix.
bool restartIsUnified(const boost::filesystem::path& prefix)
{
const auto unif = deriveFileName(prefix, { ".UNRST", ".FUNRST" });
if (unif.empty()) {
// No unified restart file matches the 'prefix'. Definitely not
// a unified restart case.
return false;
}
// There *is* a unified restart file, but there might be separate
// restart files too--e.g., if the .DATA file was modified between
// runs. Look for those, and pick the one with the most recent
// write time (i.e., stat::m_time on POSIX).
const auto separate = mostRecentSeparateRestart(prefix);
if (separate.empty()) {
// There are no separate restart files that match the 'prefix'.
// This is definitely a unified restart case.
return true;
}
// There are *both* unified and separte candidate restart files.
// Choose the unified file if more recent than most recent separate
// file.
using boost::filesystem::last_write_time;
return ! (last_write_time(unif) < last_write_time(separate));
}
boost::filesystem::path
separateRestartFile(const boost::filesystem::path& prefix,
const int reportStepID)
{
auto makeExt = [reportStepID](const std::string& cat) -> std::string
{
std::ostringstream os;
os << cat << std::setw(4) << std::setfill('0') << reportStepID;
return os.str();
};
// Formatted
const auto F = makeExt("F");
// Unformatted
const auto X = makeExt("X");
// Note: .c_str() is a bit of a hack--needed to match
// initializer_list<const char*>.
return deriveFileName(prefix, { X.c_str(), F.c_str() });
}
}
Opm::ECLCaseUtilities::ResultSet::ResultSet(const Path& casename)
: prefix_(safeCasePrefix(casename))
{
if (this->prefix_.empty()) {
throw std::invalid_argument {
casename.generic_string() +
" Is Not a Valid ECL Result Set Name"
};
}
// Don't check for unified/separate until we've verified that this is
// even a valid result set.
this->isUnified_ = restartIsUnified(this->prefix_);
}
Opm::ECLCaseUtilities::ResultSet::Path
Opm::ECLCaseUtilities::ResultSet::gridFile() const
{
return deriveFileName(this->prefix_,
{ ".EGRID", ".FEGRID",
".GRID" , ".FGRID" });
}
Opm::ECLCaseUtilities::ResultSet::Path
Opm::ECLCaseUtilities::ResultSet::initFile() const
{
return deriveFileName(this->prefix_,
{ ".INIT", ".FINIT" });
}
Opm::ECLCaseUtilities::ResultSet::Path
Opm::ECLCaseUtilities::ResultSet::restartFile(const int reportStepID) const
{
if (this->isUnifiedRestart()) {
return deriveFileName(this->prefix_, { ".UNRST", ".FUNRST" });
}
return separateRestartFile(this->prefix_, reportStepID);
}
bool
Opm::ECLCaseUtilities::ResultSet::isUnifiedRestart() const
{
return this->isUnified_;
}
std::vector<int>
Opm::ECLCaseUtilities::ResultSet::reportStepIDs() const
{
using FilePtr = ::ERT::
ert_unique_ptr<ecl_file_type, ecl_file_close>;
const auto rsspec_fn =
deriveFileName(this->prefix_, { ".RSSPEC", ".FRSSPEC" });
// Read-only, keep open between requests
const auto open_flags = 0;
auto rsspec = FilePtr{
ecl_file_open(rsspec_fn.generic_string().c_str(), open_flags)
};
auto* globView = ecl_file_get_global_view(rsspec.get());
const auto* ITIME_kw = "ITIME";
const auto n = ecl_file_view_get_num_named_kw(globView, ITIME_kw);
auto steps = std::vector<int>(n);
for (auto i = 0*n; i < n; ++i) {
const auto* itime =
ecl_file_view_iget_named_kw(globView, ITIME_kw, i);
const auto* itime_data =
static_cast<const int*>(ecl_kw_iget_ptr(itime, 0));
steps[i] = itime_data[0];
}
return steps;
}

View File

@ -0,0 +1,98 @@
/*
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_ECLCASEUTILITIES_HEADER_INCLUDED
#define OPM_ECLCASEUTILITIES_HEADER_INCLUDED
#include <vector>
#include <boost/filesystem/path.hpp>
namespace Opm { namespace ECLCaseUtilities {
/// Basic information about an ECL result set.
class ResultSet
{
public:
using Path = boost::filesystem::path;
/// Constructor.
///
/// \param[in] casename File name prefix or full path to one of a
/// result set's common representative files. Usually one of the
/// '.DATA', '.EGRID' (or .GRID) or .UNRST files.
explicit ResultSet(const Path& casename);
/// Retrieve name of result set's grid file.
Path gridFile() const;
/// Retrieve name of result set's init file.
Path initFile() const;
/// Retrieve name of result set's restart file corresponding to a
/// particular report/restart step ID.
///
/// \param[in] reportStepID Numeric report (restart) step
/// identifier. Must be non-negative. Typically one of the IDs
/// produced by member function reportStepIDs().
///
/// \return name of result set's restart file corresponding to \p
/// reportStepID.
Path restartFile(const int reportStepID) const;
/// Predicate for whether or not this particular result set uses a
/// unified restart file.
///
/// If this function returns true, then all calls to function
/// restartFile() will return the same file name. In other words,
/// the restart file need not be reopened when switching from one
/// report step to another.
///
/// \return Whether or not this result set uses a unified restart
/// file.
bool isUnifiedRestart() const;
/// Retrieve set of report step IDs stored in result set.
///
/// Starts at zero (i.e., \code reportStepIDs()[0] == 0 \endcode) if
/// the run is configured to output the initial solution state.
/// Otherwise, typically starts at one. Uses the restart
/// specification (RSSPEC) file to identify the stored report steps.
///
/// Note: Report step IDs will be increasing, but need not be
/// contiguous. Non-contiguous IDs arise when the simulation run
/// does not store all of its report/restart steps (Mnemonics
/// 'BASIC' and 'FREQ' of input keyword RPTRST).
///
/// \return Sequence of increasing report step IDs.
std::vector<int> reportStepIDs() const;
private:
/// Case prefix. Typical form is "path/to/case", but might be
/// "path/to/case.01" too.
boost::filesystem::path prefix_;
/// Whether or not this result set has a unified restart file
/// (.UNRST, .FUNRST).
bool isUnified_;
};
}} // Opm::ECLCaseUtilities
#endif // OPM_ECLCASEUTILITIES_HEADER_INCLUDED

View File

@ -102,6 +102,10 @@ public:
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const;
std::vector<double>
reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const;
private:
std::vector<double> smin_;
std::vector<double> smax_;
@ -146,6 +150,48 @@ Impl::eval(const TableEndPoints& tep,
return effsat;
}
std::vector<double>
Opm::SatFunc::TwoPointScaling::
Impl::reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const
{
const auto srng = tep.high - tep.low;
auto unscaledsat = std::vector<double>{};
unscaledsat.reserve(sp.size());
for (const auto& eval_pt : sp) {
const auto cell = eval_pt.cell;
const auto sLO = this->smin_[cell];
const auto sHI = this->smax_[cell];
unscaledsat.push_back(0.0);
auto& s_unsc = unscaledsat.back();
if (! (eval_pt.sat > tep.low)) {
// s <= minimum tabulated saturation.
// Map to Minimum Input Saturation in cell (sLO).
s_unsc = sLO;
}
else if (! (eval_pt.sat < tep.high)) {
// s >= maximum tabulated saturation.
// Map to Maximum Input Saturation in cell (sHI).
s_unsc = sHI;
}
else {
// s in tabulated interval (tep.low, tep.high)
// Map to Input Saturation in (sLO, sHI)
const auto t =
(eval_pt.sat - tep.low) / srng;
s_unsc = sLO + t*(sHI - sLO);
}
}
return unscaledsat;
}
// ---------------------------------------------------------------------
// Class Opm::ThreePointScaling::Impl
// ---------------------------------------------------------------------
@ -174,6 +220,10 @@ public:
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const;
std::vector<double>
reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const;
private:
std::vector<double> smin_;
std::vector<double> sdisp_;
@ -223,6 +273,57 @@ Impl::eval(const TableEndPoints& tep,
return effsat;
}
std::vector<double>
Opm::SatFunc::ThreePointScaling::
Impl::reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const
{
auto unscaledsat = std::vector<double>{};
unscaledsat.reserve(sp.size());
for (const auto& eval_pt : sp) {
const auto cell = eval_pt.cell;
unscaledsat.push_back(0.0);
auto& s_unsc = unscaledsat.back();
const auto sLO = this->smin_ [cell];
const auto sR = this->sdisp_[cell];
const auto sHI = this->smax_ [cell];
if (! (eval_pt.sat > tep.low)) {
// s <= minimum tabulated saturation.
// Map to Minimum Input Saturation in cell (sLO).
s_unsc = sLO;
}
else if (! (eval_pt.sat < tep.high)) {
// s >= maximum tabulated saturation.
// Map to Maximum Input Saturation in cell (sHI).
s_unsc = sHI;
}
else if (eval_pt.sat < tep.disp) {
// s in tabulated interval (tep.low, tep.disp)
// Map to Input Saturation in (sLO, sR)
const auto t =
(eval_pt.sat - tep.low)
/ (tep.disp - tep.low);
s_unsc = sLO + t*(sR - sLO);
}
else {
// s in tabulated interval (tep.disp, tep.high)
// Map to Input Saturation in (sR, sHI)
const auto t =
(eval_pt.sat - tep.disp)
/ (tep.high - tep.disp);
s_unsc = sR + t*(sHI - sR);
}
}
return unscaledsat;
}
// ---------------------------------------------------------------------
// EPS factory functions for two-point and three-point scaling options
// ---------------------------------------------------------------------
@ -1058,6 +1159,13 @@ Opm::SatFunc::TwoPointScaling::eval(const TableEndPoints& tep,
return this->pImpl_->eval(tep, sp);
}
std::vector<double>
Opm::SatFunc::TwoPointScaling::reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const
{
return this->pImpl_->reverse(tep, sp);
}
std::unique_ptr<Opm::SatFunc::EPSEvalInterface>
Opm::SatFunc::TwoPointScaling::clone() const
{
@ -1109,6 +1217,13 @@ Opm::SatFunc::ThreePointScaling::eval(const TableEndPoints& tep,
return this->pImpl_->eval(tep, sp);
}
std::vector<double>
Opm::SatFunc::ThreePointScaling::reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const
{
return this->pImpl_->reverse(tep, sp);
}
std::unique_ptr<Opm::SatFunc::EPSEvalInterface>
Opm::SatFunc::ThreePointScaling::clone() const
{

View File

@ -87,6 +87,27 @@ namespace Opm { namespace SatFunc {
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const = 0;
/// Derive unscaled (raw) input saturations from a sequence of table
/// points (independent variate in a tabulated saturation function).
///
/// This function maps the result of \code eval() \endcode back to
/// its original arguments. In other words, this is the inverse of
/// member function \code eval() \endcode.
///
/// \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 input 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>
reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const = 0;
/// Virtual copy constructor.
virtual std::unique_ptr<EPSEvalInterface> clone() const = 0;
/// Destructor. Must be virtual.
@ -152,7 +173,7 @@ namespace Opm { namespace SatFunc {
/// 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.
/// \code tep.disp \endcode is never referenced.
///
/// \param[in] sp Sequence of saturation points. The maximum cell
/// index (\code sp[i].cell \endcode) must be strictly less than
@ -166,6 +187,29 @@ namespace Opm { namespace SatFunc {
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const override;
/// Derive unscaled (raw) input saturations from a sequence of table
/// points (independent variate in a tabulated saturation function).
///
/// This function maps the result of \code eval() \endcode back to
/// its original arguments. In other words, this is the inverse of
/// member function \code eval() \endcode.
///
/// \param[in] tep Static end points that identify the saturation
/// scaling intervals of a particular tabulated saturation
/// function. The reverse mapping procedure considers only \code
/// tep.low \endcode and \code tep.high \endcode. The value of
/// \code tep.disp \endcode is never referenced.
///
/// \param[in] sp Sequence of saturation points.
///
/// \return Sequence of input 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>
reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const override;
/// Virtual copy constructor.
virtual std::unique_ptr<EPSEvalInterface> clone() const override;
private:
@ -237,9 +281,9 @@ namespace Opm { namespace SatFunc {
///
/// \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.
/// function. All defined end points---\code tep.low \endcode,
/// \code \tep.disp \endcode, and \code tep.high \endcode---are
/// relevant and referenced in the evaluation procedure.
///
/// \param[in] sp Sequence of saturation points. The maximum cell
/// index (\code sp[i].cell \endcode) must be strictly less than
@ -253,6 +297,29 @@ namespace Opm { namespace SatFunc {
eval(const TableEndPoints& tep,
const SaturationPoints& sp) const override;
/// Derive unscaled (raw) input saturations from a sequence of table
/// points (independent variate in a tabulated saturation function).
///
/// This function maps the result of \code eval() \endcode back to
/// its original arguments. In other words, this is the inverse of
/// member function \code eval() \endcode.
///
/// \param[in] tep Static end points that identify the saturation
/// scaling intervals of a particular tabulated saturation
/// function. All defined end points---\code tep.low \endcode,
/// \code \tep.disp \endcode, and \code tep.high \endcode---are
/// relevant and referenced in the reverse mapping procedure.
///
/// \param[in] sp Sequence of saturation points.
///
/// \return Sequence of input 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>
reverse(const TableEndPoints& tep,
const SaturationPoints& sp) const override;
/// Virtual copy constructor.
virtual std::unique_ptr<EPSEvalInterface> clone() const override;
private:

View File

@ -18,22 +18,186 @@
*/
#include <opm/utility/ECLFluxCalc.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <algorithm>
#include <exception>
#include <functional>
#include <iterator>
#include <stdexcept>
#include <utility>
#include <ert/ecl/ecl_kw_magic.h>
namespace {
std::vector<double>
computeGravDZ(const std::vector<int>& neigh,
const double grav,
const std::vector<double>& depth)
{
const auto nf = neigh.size() / 2;
auto gdz = std::vector<double>{};
gdz.reserve(nf);
for (auto f = 0*nf; f < nf; ++f) {
const auto c1 = neigh[2*f + 0];
const auto c2 = neigh[2*f + 1];
gdz.push_back(grav * (depth[c2] - depth[c1]));
}
return gdz;
}
std::vector<int>
pvtnumVector(const ::Opm::ECLGraph& G,
const ::Opm::ECLInitFileData& init)
{
auto pvtnum = G.rawLinearisedCellData<int>(init, "PVTNUM");
if (pvtnum.empty()) {
// PVTNUM missing in one or more of the grids managed by 'G'.
// Put all cells in PVTNUM region 1.
pvtnum.assign(G.numCells(), 1);
}
return pvtnum;
}
std::vector<double>
depthVector(const ::Opm::ECLGraph& G,
const ::Opm::ECLInitFileData& init)
{
// Note: ECLGraph does not support unit conversion of INIT data so
// we need to perform the requisite conversions ourselves.
auto depth = G.rawLinearisedCellData<double>(init, "DEPTH");
if (depth.empty()) {
// DEPTH missing in one or more of the grids managed by 'G'.
// Put all cells at zero depth, which turns off gravity.
depth.assign(G.numCells(), 0.0);
}
const auto& ih = init.keywordData<int>(INTEHEAD_KW);
const auto usys = ::Opm::ECLUnits::
createUnitSystem(ih[ INTEHEAD_UNIT_INDEX ]);
const auto depthscale = usys->depth();
for (auto& zi : depth) {
zi = ::Opm::unit::convert::from(zi, depthscale);
}
return depth;
}
std::vector<double>
disgasVector(const ::Opm::ECLGraph& G,
const bool is_liveoil,
const ::Opm::ECLRestartData& rstrt)
{
auto disgas = std::vector<double>{};
if (! is_liveoil) {
// Oil model does not use dissolved gas. We don't need to
// provide Rs data, so return zero (simplifies calling code).
disgas.assign(G.numCells(), 0.0);
return disgas;
}
// Gas model does use vaporised oil. Extract Rs data or throw if
// unavailable.
disgas = G.linearisedCellData(rstrt, "RS",
&::Opm::ECLUnits::UnitSystem::
dissolvedGasOilRat);
if (disgas.empty()) {
throw std::invalid_argument {
"Restart Data Does Not Provide "
"Dissolved Gas/Oil Ratio Data "
"for Live Oil PVT Model"
};
}
return disgas;
}
std::vector<double>
vapoilVector(const ::Opm::ECLGraph& G,
const bool is_wetgas,
const ::Opm::ECLRestartData& rstrt)
{
auto vapoil = std::vector<double>{};
if (! is_wetgas) {
// Gas model does not use vaporised oil. We don't need to
// provide Rv data, so return zero (simplifies calling code).
vapoil.assign(G.numCells(), 0.0);
return vapoil;
}
// Gas model does use vaporised oil. Extract Rv data or throw if
// unavailable.
vapoil = G.linearisedCellData(rstrt, "RV",
&::Opm::ECLUnits::UnitSystem::
vaporisedOilGasRat);
if (vapoil.empty()) {
throw std::invalid_argument {
"Restart Data Does Not Provide "
"Vaporised Oil/Gas Ratio Data "
"for Wet Gas PVT Model"
};
}
return vapoil;
}
template <class PVTPtr>
void verify_active_phase(const PVTPtr& pvt,
const std::string& phase)
{
if (! pvt) {
throw std::logic_error {
"Cannot Compute " + phase +
" PVT Unless " + phase +
" is an Active Phase"
};
}
}
} // Anonymous
namespace Opm
{
ECLFluxCalc::ECLFluxCalc(const ECLGraph& graph,
ECLSaturationFunc&& satfunc)
ECLFluxCalc::ECLFluxCalc(const ECLGraph& graph,
const ECLInitFileData& init,
const double grav,
const bool useEPS)
: graph_(graph)
, satfunc_(std::move(satfunc))
, satfunc_(graph, init, useEPS)
, rmap_(pvtnumVector(graph, init))
, neighbours_(graph.neighbours())
, transmissibility_(graph.transmissibility())
, gravDz_(computeGravDZ(neighbours_, grav, depthVector(graph, init)))
, pvtGas_(ECLPVT::CreateGasPVTInterpolant::fromECLOutput(init))
, pvtOil_(ECLPVT::CreateOilPVTInterpolant::fromECLOutput(init))
, pvtWat_(ECLPVT::CreateWaterPVTInterpolant::fromECLOutput(init))
{
const auto& lh = init.keywordData<bool>(LOGIHEAD_KW);
this->disgas_ = lh[ LOGIHEAD_RS_INDEX ]; // Live Oil?
this->vapoil_ = lh[ LOGIHEAD_RV_INDEX ]; // Wet Gas?
}
@ -42,16 +206,10 @@ namespace Opm
std::vector<double>
ECLFluxCalc::flux(const ECLRestartData& rstrt,
const ECLPhaseIndex phase) const
const ECLPhaseIndex phase) const
{
// Obtain dynamic data.
DynamicData dyn_data;
dyn_data.pressure = graph_
.linearisedCellData(rstrt, "PRESSURE",
&ECLUnits::UnitSystem::pressure);
dyn_data.relperm = this->satfunc_
.relperm(this->graph_, rstrt, phase);
const auto dyn_data = this->phaseProperties(rstrt, phase);
// Compute fluxes per connection.
const int num_conn = transmissibility_.size();
@ -71,16 +229,282 @@ namespace Opm
{
const int c1 = neighbours_[2*connection];
const int c2 = neighbours_[2*connection + 1];
const double transmissibility = transmissibility_[connection];
const double viscosity = 1.0 * prefix::centi * unit::Poise;
const auto& pressure = dyn_data.pressure;
const int upwind_cell = (pressure[c2] > pressure[c1]) ? c2 : c1;
const double kr = dyn_data.relperm[upwind_cell];
// Phase pressure in connecting cells.
const auto p1 = dyn_data.pressure[c1];
const auto p2 = dyn_data.pressure[c2];
const double mobility = kr / viscosity;
return mobility * transmissibility * (pressure[c1] - pressure[c2]);
// Phase density at interface: Arith. avg. of cell values.
const auto rho =
(dyn_data.density[c1] + dyn_data.density[c2]) / 2.0;
// Phase potential drop across interface.
const auto dh = p1 - p2 + rho*this->gravDz_[connection];
// Phase mobility at interface: Upstream weighting (phase pot).
const auto ucell = (dh < 0.0) ? c2 : c1;
const auto mob = dyn_data.mobility[ucell];
// Background (static) transmissibility.
const auto T = this->transmissibility_[connection];
return mob * T * dh;
}
ECLFluxCalc::DynamicData
ECLFluxCalc::phaseProperties(const ECLRestartData& rstrt,
const ECLPhaseIndex phase) const
{
auto dyn_data = DynamicData{};
// Step 1 of Phase Pressure Calculation.
// Retrieve oil pressure directly from result set.
dyn_data.pressure = this->graph_
.linearisedCellData(rstrt, "PRESSURE",
&ECLUnits::UnitSystem::pressure);
// Step 1 of Mobility Calculation.
// Store phase's relative permeability values.
dyn_data.mobility =
this->satfunc_.relperm(this->graph_, rstrt, phase);
// Step 1 of Mass Density (Reservoir Conditions) Calculation.
// Allocate space for storing the cell values.
dyn_data.density.assign(this->graph_.numCells(), 0.0);
switch (phase) {
case ECLPhaseIndex::Aqua:
return this->watPVT(std::move(dyn_data));
case ECLPhaseIndex::Liquid:
return this->oilPVT(rstrt, std::move(dyn_data));
case ECLPhaseIndex::Vapour:
return this->gasPVT(rstrt, std::move(dyn_data));
}
throw std::invalid_argument {
"phaseProperties(): Invalid Phase Identifier"
};
}
ECLFluxCalc::DynamicData
ECLFluxCalc::gasPVT(const ECLRestartData& rstrt,
DynamicData&& dyn_data) const
{
verify_active_phase(this->pvtGas_, "Gas");
const auto rv = vapoilVector(this->graph_, this->vapoil_, rstrt);
this->regionLoop([this, &rv, &dyn_data]
(const int regID)
{
// Note: This function assumes that 'regID' is a traditional
// ECL-style one-based region ID such as PVTNUM. Subtract one,
// where approriate, to generate zero-based region indices.
const auto Rv = ECLPVT::Gas::VaporizedOil {
this->gatherRegionSubset(regID, rv)
};
const auto Pg = ECLPVT::Gas::GasPressure {
// Cheating. This is Po.
this->gatherRegionSubset(regID, dyn_data.pressure)
};
// Mass Density at Reservoir Conditions. Relies on setup code
// having allocated sufficient space.
{
const auto rhoOS = this->vapoil_
? this->pvtOil_->surfaceMassDensity(regID - 1)
: 0.0;
const auto rhoGS =
this->pvtGas_->surfaceMassDensity(regID - 1);
const auto Bg = this->pvtGas_
->formationVolumeFactor(regID - 1, Rv, Pg);
auto rhoGr = std::vector<double>{};
rhoGr.reserve(Bg.size());
std::transform(std::begin(Bg),
std::end (Bg),
std::begin(Rv.data),
std::back_inserter(rhoGr),
[rhoOS, rhoGS]
(const double Bg_i, const double Rv_i)
{
return (rhoOS*Rv_i + rhoGS) / Bg_i;
});
this->scatterRegionResults(regID, rhoGr, dyn_data.density);
}
// Convert relative permeability values into mobility values
// (divide by phase viscosity). Relies on setup code having
// computed relative permeability for the phase.
{
const auto mu = this->pvtGas_->viscosity(regID - 1, Rv, Pg);
this->computePhaseMobility(regID, mu, dyn_data);
}
});
return std::move(dyn_data);
}
ECLFluxCalc::DynamicData
ECLFluxCalc::oilPVT(const ECLRestartData& rstrt,
DynamicData&& dyn_data) const
{
verify_active_phase(this->pvtOil_, "Oil");
const auto rs = disgasVector(this->graph_, this->disgas_, rstrt);
this->regionLoop([this, &rs, &dyn_data]
(const int regID)
{
// Note: This section assumes that 'regID' is a traditional
// ECL-style one-based region ID such as PVTNUM. Subtract one,
// where approriate, to generate zero-based region indices.
const auto Rs = ECLPVT::Oil::DissolvedGas {
this->gatherRegionSubset(regID, rs)
};
const auto Po = ECLPVT::Oil::OilPressure {
// Recall: dyn_data.pressure is Po directly from 'rstrt'.
this->gatherRegionSubset(regID, dyn_data.pressure)
};
// Mass Density at Reservoir Conditions. Relies on setup code
// having allocated sufficient space.
{
const auto rhoOS =
this->pvtOil_->surfaceMassDensity(regID - 1);
const auto rhoGS = this->disgas_
? this->pvtGas_->surfaceMassDensity(regID - 1)
: 0.0;
const auto Bo = this->pvtOil_
->formationVolumeFactor(regID - 1, Rs, Po);
auto rhoOr = std::vector<double>{};
rhoOr.reserve(Bo.size());
std::transform(std::begin(Bo),
std::end (Bo),
std::begin(Rs.data),
std::back_inserter(rhoOr),
[rhoOS, rhoGS]
(const double Bo_i, const double Rs_i)
{
return (rhoOS + rhoGS*Rs_i) / Bo_i;
});
this->scatterRegionResults(regID, rhoOr, dyn_data.density);
}
// Convert relative permeability values into mobility values
// (divide by phase viscosity). Relies on setup code having
// computed relative permeability for the phase.
{
const auto mu = this->pvtOil_->viscosity(regID - 1, Rs, Po);
this->computePhaseMobility(regID, mu, dyn_data);
}
});
return std::move(dyn_data);
}
ECLFluxCalc::DynamicData
ECLFluxCalc::watPVT(DynamicData&& dyn_data) const
{
verify_active_phase(this->pvtWat_, "Water");
this->regionLoop([this, &dyn_data]
(const int regID)
{
// Note: This section assumes that 'regID' is a traditional
// ECL-style one-based region ID such as PVTNUM. Subtract one,
// where approriate, to generate zero-based region indices.
const auto Pw = ECLPVT::Water::WaterPressure {
// Cheating. This is Po.
this->gatherRegionSubset(regID, dyn_data.pressure)
};
// Mass Density at Reservoir Conditions. Relies on setup code
// having allocated sufficient space.
{
const auto rhoWS =
this->pvtWat_->surfaceMassDensity(regID - 1);
const auto Bw = this->pvtWat_
->formationVolumeFactor(regID - 1, Pw);
auto rhoWr = std::vector<double>{};
rhoWr.reserve(Bw.size());
std::transform(std::begin(Bw),
std::end (Bw),
std::back_inserter(rhoWr),
[rhoWS](const double Bw_i)
{
return rhoWS / Bw_i;
});
this->scatterRegionResults(regID, rhoWr, dyn_data.density);
}
// Convert relative permeability values into mobility values
// (divide by phase viscosity). Relies on setup code having
// computed relative permeability for the phase.
{
const auto mu = this->pvtWat_->viscosity(regID - 1, Pw);
this->computePhaseMobility(regID, mu, dyn_data);
}
});
return std::move(dyn_data);
}
void
ECLFluxCalc::computePhaseMobility(const int regID,
const std::vector<double>& mu,
DynamicData& dyn_data) const
{
auto kr = this->gatherRegionSubset(regID, dyn_data.mobility);
std::transform(std::begin(kr), std::end (kr),
std::begin(mu), std::begin(kr),
std::divides<double>());
this->scatterRegionResults(regID, kr, dyn_data.mobility);
}
} // namespace Opm

View File

@ -22,23 +22,41 @@
#include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLPvtGas.hpp>
#include <opm/utility/ECLPvtOil.hpp>
#include <opm/utility/ECLPvtWater.hpp>
#include <opm/utility/ECLRegionMapping.hpp>
#include <opm/utility/ECLSaturationFunc.hpp>
#include <memory>
#include <vector>
namespace Opm
{
class ECLRestartData;
class ECLInitFileData;
/// Class for computing connection fluxes in the absence of flux output.
class ECLFluxCalc
{
public:
/// Construct from ECLGraph.
/// Construct from ECLGraph and Run Initialization Data.
///
/// \param[in] graph Connectivity data, as well as providing a means to read data from the restart file.
explicit ECLFluxCalc(const ECLGraph& graph,
ECLSaturationFunc&& satfunc);
/// \param[in] graph Connectivity data, as well as providing a means
/// to read data from the restart file.
///
/// \param[in] init ECLIPSE result set static initialization data
/// ("INIT" file).
///
/// \param[in] grav Gravity constant (9.80665 m/s^2 at Tellus equator).
///
/// \param[in] useEPS Whether or not to include effects of
/// saturation function end-point scaling if activated in the
/// result set.
ECLFluxCalc(const ECLGraph& graph,
const ECLInitFileData& init,
const double grav,
const bool useEPS);
/// Retrive phase flux on all connections defined by \code
/// graph.neighbours() \endcode.
@ -59,16 +77,79 @@ namespace Opm
struct DynamicData
{
std::vector<double> pressure;
std::vector<double> relperm;
std::vector<double> mobility;
std::vector<double> density;
};
double singleFlux(const int connection,
const DynamicData& dyn_data) const;
DynamicData phaseProperties(const ECLRestartData& rstrt,
const ECLPhaseIndex phase) const;
DynamicData gasPVT(const ECLRestartData& rstrt,
DynamicData&& dyn_data) const;
DynamicData oilPVT(const ECLRestartData& rstrt,
DynamicData&& dyn_data) const;
DynamicData watPVT(DynamicData&& dyn_data) const;
void computePhaseMobility(const int regID,
const std::vector<double>& mu,
DynamicData& dyn_data) const;
template <typename T>
std::vector<T>
gatherRegionSubset(const int reg,
const std::vector<T>& x) const
{
auto y = std::vector<T>{};
if (x.empty()) {
return y;
}
for (const auto& ix : this->rmap_.getRegionIndices(reg)) {
y.push_back(x[ix]);
}
return y;
}
template <typename T>
void scatterRegionResults(const int reg,
const std::vector<T>& x_reg,
std::vector<T>& x) const
{
auto i = static_cast<decltype(x_reg.size())>(0);
for (const auto& ix : this->rmap_.getRegionIndices(reg)) {
x[ix] = x_reg[i++];
}
}
template <class RegOp>
void regionLoop(RegOp&& regOp) const
{
for (const auto& regID : this->rmap_.activeRegions()) {
regOp(regID);
}
}
const ECLGraph& graph_;
ECLSaturationFunc satfunc_;
ECLRegionMapping rmap_;
std::vector<int> neighbours_;
std::vector<double> transmissibility_;
std::vector<double> gravDz_;
bool disgas_{false};
bool vapoil_{false};
std::unique_ptr<ECLPVT::Gas> pvtGas_;
std::unique_ptr<ECLPVT::Oil> pvtOil_;
std::unique_ptr<ECLPVT::Water> pvtWat_;
};
} // namespace Opm

View File

@ -0,0 +1,348 @@
/*
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_ECLSIMPLE1DINTERPOLANT_HEADER_INCLUDED
#define OPM_ECLSIMPLE1DINTERPOLANT_HEADER_INCLUDED
#include <opm/utility/ECLTableInterpolation1D.hpp>
#include <cassert>
#include <cmath>
#include <exception>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
namespace Opm { namespace Interp1D { namespace PiecewisePolynomial {
/// Piecewise linear interpolation in set of result columns.
///
/// \tparam Extrapolation Policy class for determining how to
/// extrapolate results outside the range covered by the independent
/// variate. Must support member functions \c left and \c right that
/// extrapolate a tabulated function to the left and right of the
/// input range, respectively. Typically a policy class from
/// namespace ExtrapolationPolicy.
///
/// \tparam IsAscendingRange Flag for whether or not the input range is
/// sorted ascendingly or descendingly. Class \c Linear assumes that
/// the input range is sorted, so if \code IsAscendingRange = false
/// \endcode, this implies that the input range is treated as being
/// sorted descendingly (i.e., as if by \code std::sort(begin, end,
/// std::greater<>{}) \endcode.
template <class Extrapolation, bool IsAscendingRange = true>
class Linear
{
public:
/// Constructor.
///
/// Essentially a hack. This creates an invalid interpolant and
/// exists only to support creating an object backed by an empty
/// range that will be subsequently discarded and not actually used.
/// This, in turn, is useful in the construction of live oil/wet gas
/// property interpolants that have padded tables.
///
/// \param[in] extrap Instance of the configured extrapolation
/// policy class.
explicit Linear(Extrapolation&& extrap)
: extrap_(std::forward<Extrapolation>(extrap))
, nCols_ (0)
{}
/// Constructor.
///
/// \tparam ElmIterator Iterator over the elements of an input range
/// or a result column. Typically \code
/// std::vector<double>::const_iterator \endcode.
///
/// \tparam ValueTransform Representation of a value transformation
/// that, often, effects unit conversion of input tables.
/// Assumed to implement a function call operator that supports
/// the syntax
/// \code
/// w = ValueTransform(v)
/// \endcode
///
/// \param[in] extrap Instance of the configured extrapolation
/// policy class.
///
/// \param[in] xBegin Start of range of independent variate.
///
/// \param[in] xEnd One past the end of the range of indpendent
/// variate.
///
/// \param[in,out] colIt Sequence of ranges of dependent variates.
/// On input, points to beginning of ranges. On output, each
/// range pointer is advanced across \code std::distance(xBegin,
/// xEnd) \endcode entries. This assumes that the underlying
/// ranges are formatted according to ECL result set conventions
/// (TAB vector from the INIT file).
///
/// \param[in] xTransform Value transformation for the independent
/// variate. Called for each "valid" element of the input range.
///
/// \param[in] colTransform Sequence of value transformations for
/// the dependent variates. In particular, \code
/// colTransform[i]() \endcode is invoked on the \c i-th
/// dependent variate if and only if the corresponding element of
/// the input range is "valid".
template <class ElmIterator, class ValueTransform>
Linear(Extrapolation&& extrap,
ElmIterator xBegin,
ElmIterator xEnd,
std::vector<ElmIterator>& colIt,
const ValueTransform& xTransform,
const std::vector<ValueTransform>& colTransform);
/// Classify an input point according to the range of the
/// interpolant's configured independent variate.
///
/// Classification is performed according to the configured policy
/// for treating the sort order of the input range.
///
/// \param[in] x Input point.
///
/// \return Classification of the input point \p x.
LocalInterpPoint classifyPoint(const double x) const
{
return LocalInterpPoint::identify(this->x_, x, Ascending_P{});
}
/// Evaluate interpolant of particular dependent variable at
/// particular input point.
///
/// \param[in] col Column ID of particular dependent variable.
///
/// \param[in] pt Input point. Result of previous call to member
/// function \code classifyPoint() \endcode.
///
/// \return Value of dependent variable \p col at interpolation
/// point \p pt.
double evaluate(const std::size_t col,
const LocalInterpPoint& pt) const
{
if (pt.cat == PointCategory::InRange) {
// Common case. Input point is within range of x_. Placed
// first to enable early return.
return this->interpolate(pt, col);
}
auto yval = [this](const std::size_t i,
const std::size_t j) -> double
{
return this->y(i, j);
};
if (pt.cat == PointCategory::LeftOfRange) {
// Extrapolate function ot the left of input range.
const auto xmin = this->x_.front();
return this->extrap_.left(this->x_, xmin + pt.t, col, yval);
}
assert (pt.cat == PointCategory::RightOfRange);
// Extrapolate function ot the right of input range.
const auto xmax = this->x_.back();
return this->extrap_.right(this->x_, xmax + pt.t, col, yval);
}
/// Retrieve abscissas of interpolant's independent variate.
const std::vector<double>& independentVariable() const
{
return this->x_;
}
/// Retrieve ordinates of one of the interpolant's dependent
/// variates.
///
/// \param[in] col Column ID of particular dependent variable.
///
/// \return Ordinates corresponding to particular dependent variate,
/// with the \c i-th element matching the \c i-th element of the
/// independent variate.
std::vector<double> resultVariable(const std::size_t col) const
{
auto result = std::vector<double>{};
if (col >= this->nCols_) {
throw std::domain_error {
"Result Column Identifier Ouf of Bounds"
};
}
result.reserve(this->x_.size());
for (auto n = this->x_.size(), i = 0*n; i < n; ++i) {
result.push_back(this->y(i, col));
}
return result;
}
private:
/// Predicate for ascendingly sorted input ranges. True_type for
/// ascending ranges, false_type for descendingly sorted ranges.
using Ascending_P =
std::integral_constant<bool, IsAscendingRange>;
/// Instance of extrapolation policy object. Invoked when input
/// points are outside the range of the table's independent variate.
Extrapolation extrap_;
/// Number of result columns.
std::size_t nCols_;
/// Abscissas (independent variate), compressed to valid points
/// only.
std::vector<double> x_;
/// Ordinates of dependent variates, compressed to valid points of
/// input range. Stored with column index (dependent variate ID)
/// cycling the most rapidly.
std::vector<double> y_;
/// Evaluate interpolant of particular dependent variable at
/// particular input point.
///
/// Implements case of input point being within range of table's
/// independent variate.
///
/// \param[in] col Column ID of particular dependent variable.
///
/// \param[in] pt Input point. Result of previous call to member
/// function \code classifyPoint() \endcode.
///
/// \return Value of dependent variable \p col at interpolation
/// point \p pt.
double interpolate(const LocalInterpPoint& pt,
const std::size_t col) const
{
assert (pt.cat == PointCategory::InRange);
assert (pt.interval + 1 < this->x_.size());
const auto left = pt.interval + 0;
const auto xl = this->x_[left];
const auto yl = this->y (left, col);
const auto right = pt.interval + 1;
const auto xr = this->x_[right];
const auto yr = this->y (right, col);
const auto t = pt.t / (xr - xl);
return t*yr + (1.0 - t)*yl;
}
/// Retrieve value of ordinate at specified row and column pair.
///
/// \param[in] row Row index.
///
/// \param[in] col Column index.
///
/// \return Ordinate at index \code (row, col) \endcode.
double y(const std::size_t row,
const std::size_t col) const
{
// Recall: this->y_ stored with column index cycling the most
// rapidly.
assert (col < this->nCols_);
assert (row*this->nCols_ + col < this->y_.size());
return this->y_[row*this->nCols_ + col];
}
};
template <class Extrapolation, bool IsAscendingRange>
template <class ElmIterator, class ValueTransform>
Linear<Extrapolation, IsAscendingRange>::
Linear(Extrapolation&& extrap,
ElmIterator xBegin,
ElmIterator xEnd,
std::vector<ElmIterator>& colIt,
const ValueTransform& xTransform,
const std::vector<ValueTransform>& colTransform)
: extrap_(std::forward<Extrapolation>(extrap))
, nCols_ (colIt.size())
{
// 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(xTransform(*xBegin));
auto colID = 0*colTransform.size();
for (auto ci : colIt) {
// Store 'y_' with column index cycling most rapidly.
this->y_.push_back(colTransform[colID++](*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"
};
}
}
}}} // Opm::Interp1D::PiecewisePolynomial
#endif // OPM_ECLSIMPLE1DINTERPOLANT_HEADER_INCLUDED

View File

@ -19,147 +19,30 @@
#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,
const ConvertUnits& convert,
std::vector<ElmIt>& colIt)
: interp_(Extrap{}, xBegin, xEnd, colIt,
convert.indep, convert.column)
{
// 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
interpolate(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();
const auto pt = this->interp_.classifyPoint(xi);
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);
}
y.push_back(this->interp_.evaluate(c.i, pt));
}
return y;
@ -168,13 +51,12 @@ interpolate(const ECLPropTableRawData::SizeType nCols,
double
Opm::SatFuncInterpolant::SingleTable::connateSat() const
{
return this->x_.front();
return this->interp_.independentVariable().front();
}
double
Opm::SatFuncInterpolant::SingleTable::
criticalSat(const ECLPropTableRawData::SizeType nCols,
const ResultColumn& c) const
criticalSat(const ResultColumn& c) const
{
// Note: Relative permeability functions are presented as non-decreasing
// functions of the corresponding phase saturation. The internal table
@ -184,11 +66,13 @@ criticalSat(const ECLPropTableRawData::SizeType nCols,
// 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();
const auto y = this->interp_.resultVariable(c.i);
const auto nRows = y.size();
auto row = 0 * nRows;
for (; row < nRows; ++row) {
if (this->y(nCols, row, c) > 0.0) { break; }
if (y[row] > 0.0) { break; }
}
if (row == 0) {
@ -197,52 +81,48 @@ criticalSat(const ECLPropTableRawData::SizeType nCols,
};
}
return this->x_[row - 1];
return this->interp_.independentVariable()[row - 1];
}
double
Opm::SatFuncInterpolant::SingleTable::maximumSat() const
{
return this->x_.back();
return this->interp_.independentVariable().back();
}
const std::vector<double>&
Opm::SatFuncInterpolant::SingleTable::saturationPoints() const
{
return this->interp_.independentVariable();
}
// =====================================================================
Opm::SatFuncInterpolant::SatFuncInterpolant(const ECLPropTableRawData& raw)
Opm::SatFuncInterpolant::SatFuncInterpolant(const ECLPropTableRawData& raw,
const ConvertUnits& convert)
: nResCols_(raw.numCols - 1)
{
using ElmIt = ::Opm::ECLPropTableRawData::ElementIterator;
if (raw.numPrimary != 1) {
throw std::invalid_argument {
"Saturation Interpolant Does Not Support Multiple Sub-Tables"
};
}
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)
this->table_ = MakeInterpolants<SingleTable>::fromRawData(raw,
[&convert](ElmIt xBegin, ElmIt xEnd, std::vector<ElmIt>& colIt)
{
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));
}
// Note: this constructor needs to advance each 'colIt' across
// distance(xBegin, xEnd) entries.
return SingleTable(xBegin, xEnd, convert, colIt);
});
}
std::vector<double>
@ -262,7 +142,7 @@ Opm::SatFuncInterpolant::interpolate(const InTable& t,
};
}
return this->table_[t.i].interpolate(this->nResCols_, c, x);
return this->table_[t.i].interpolate(c, x);
}
std::vector<double>
@ -285,7 +165,7 @@ Opm::SatFuncInterpolant::criticalSat(const ResultColumn& c) const
scrit.reserve(this->table_.size());
for (const auto& t : this->table_) {
scrit.push_back(t.criticalSat(this->nResCols_, c));
scrit.push_back(t.criticalSat(c));
}
return scrit;
@ -303,3 +183,15 @@ Opm::SatFuncInterpolant::maximumSat() const
return smax;
}
const std::vector<double>&
Opm::SatFuncInterpolant::saturationPoints(const InTable& t) const
{
if (t.i >= this->table_.size()) {
throw std::invalid_argument {
"Invalid Table ID"
};
}
return this->table_[t.i].saturationPoints();
}

View File

@ -20,6 +20,10 @@
#ifndef OPM_ECLPROPTABLE_HEADER_INCLUDED
#define OPM_ECLPROPTABLE_HEADER_INCLUDED
#include <opm/utility/ECLPiecewiseLinearInterpolant.hpp>
#include <opm/utility/ECLTableInterpolation1D.hpp>
#include <functional>
#include <vector>
/// \file
@ -43,19 +47,117 @@ namespace Opm {
/// Raw table data. Column major (Fortran) order. Typically
/// copied/extracted directly from TAB vector of INIT result-set.
DataVector data;
/// Array of size \code numRows * numCols * numPrimary \endcode for
/// each table, stored consecutively.
DataVector data{};
/// Primary lookup key for 2D interpolation. Only relevant for PVT
/// tables of wet gas and/or live oil (Pg or Rs, respectively).
/// Array of size \c numPrimary elements for each table, stored
/// consecutively.
DataVector primaryKey{};
/// Number of primary key elements for each individual table. Only
/// relevant (i.e., != 1) for PVT tables of wet gas and/or live oil.
SizeType numPrimary{0};
/// 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;
/// primary key. Typically corresponds to setting in one of the
/// *DIMS keywords. Should normally be at least two for saturation
/// functions.
SizeType numRows{0};
/// Number of columns in this table. Varies by keyword/table.
SizeType numCols;
SizeType numCols{0};
/// Number of tables of this type. Must match the corresponding
/// region keyword.
SizeType numTables;
SizeType numTables{0};
};
/// Build a sequence of table interpolants from raw tabulated data,
/// assuming table conventions in the INIT file's TABDIMS/TAB vectors.
///
/// \tparam Interpolant Representation of a table interpolant.
template <class Interpolant>
struct MakeInterpolants
{
/// Create sequence of table interpolants.
///
/// This function is aware of the internal layout of the INIT file's
/// tabulated function and knows how to identify table data ranges
/// corresponding to a single table. In particular we know how to
/// the data according to region IDs and how to apply further
/// partitioning according to primary lookup keys (e.g., for RS
/// nodes in PVTO tables).
///
/// \tparam Factory Interpolant construction function. Usually a
/// class constructor wrapped in a lambda. The call
/// \code
/// I = Factory(xBegin, xEnd, colIt);
/// \endcode
/// must construct an instance \c I of type \p Interpolant.
/// Here, \c xBegin and \c xEnd demarcate the range of a single
/// table's independent variate and \c colIt are column iterators
/// positioned at the beginning of each of the table's dependent
/// (result) column.
///
/// Note: The construction function is expected to advance each
/// column iterator across \code distance(xBegin, xEnd) \endcode
/// entries.
///
/// \param[in] raw Raw tabulated data. Must correspond to a single
/// table vector, e.g. the SWFN data.
///
/// \param[in] construct Callback function that knows how to build a
/// single interpolant given a sequence ranges of of independent
/// and dependent tabulated function values. Must advance the
/// dependent column iterators and perform appropriate unit
/// conversion on the table data if needed (e.g., for capillary
/// pressure data or viscosity values).
template <class Factory>
static std::vector<Interpolant>
fromRawData(const ECLPropTableRawData& raw,
Factory&& construct)
{
auto interp = std::vector<Interpolant>{};
const auto numInterp = raw.numTables * raw.numPrimary;
// Table format: numRows*numInterp values of first column
// (indep. var) followed by numCols-1 dependent variable
// (function value result) columns of numRows*numInterp values
// each, one column at a time.
const auto colStride = raw.numRows * numInterp;
// 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);
}
// Construct actual interpolants by invoking the
// constructor/factory function on each sub-table.
for (auto i = 0*numInterp;
i < numInterp; ++i, xBegin += raw.numRows)
{
auto xEnd = xBegin + raw.numRows;
// Layering violation:
// The constructor is expected to advance the result
// column iterators across 'numRows' entries.
interp.push_back(construct(xBegin, xEnd, colIt));
}
return interp;
}
};
/// Collection of 1D interpolants from tabulated functions (e.g., the
@ -63,10 +165,29 @@ namespace Opm {
class SatFuncInterpolant
{
public:
/// Protocol for converting raw table input data to strict SI unit
/// conventions.
struct ConvertUnits
{
/// Convenience type alias for a value transformation.
using Converter = std::function<double(const double)>;
/// How to convert the independent variate (1st column)
Converter indep;
/// How to convert the dependent variates (2nd... columns).
std::vector<Converter> column;
};
/// Constructor.
///
/// \param[in] raw Raw table data for this collection.
explicit SatFuncInterpolant(const ECLPropTableRawData& raw);
///
/// \param[in] convert Unit conversion support. Mostly applicable
/// to capillary pressure. Assumed to convert raw table data to
/// strict SI unit conventions.
SatFuncInterpolant(const ECLPropTableRawData& raw,
const ConvertUnits& convert);
/// Wrapper type to disambiguate API usage. Represents a table ID.
struct InTable {
@ -105,6 +226,15 @@ namespace Opm {
/// Retrieve maximum saturation in all tables.
std::vector<double> maximumSat() const;
/// Retrieve unscaled sample points of independent variable in
/// particular sub-table (saturation region).
///
/// \param[in] t ID of sub-table of interpolant.
///
/// \return Abscissas of tabulated saturation function corresponding
/// to particular saturation region.
const std::vector<double>& saturationPoints(const InTable& t) const;
private:
/// Single tabulated 1D interpolant.
class SingleTable
@ -120,6 +250,10 @@ namespace Opm {
/// \param[in] xEnd One past the end of linear range of
/// independent variable values.
///
/// \param[in] convert Unit conversion support. Mostly
/// applicable to capillary pressure. Assumed to convert raw
/// table data to strict SI unit conventions.
///
/// \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
@ -128,6 +262,7 @@ namespace Opm {
/// for the next table if relevant (and called in a loop).
SingleTable(ElmIt xBegin,
ElmIt xEnd,
const ConvertUnits& convert,
std::vector<ElmIt>& colIt);
/// Evaluate 1D interpolant in sequence of points.
@ -141,33 +276,32 @@ namespace Opm {
/// \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;
interpolate(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;
double criticalSat(const ResultColumn& c) const;
/// Retrieve maximum saturation in table.
double maximumSat() const;
/// Retrieve unscaled sample points of independent variable.
const std::vector<double>& saturationPoints() const;
private:
/// Independent variable.
std::vector<double> x_;
/// Extrapolation policy for property evaluator/interpolant.
using Extrap = ::Opm::Interp1D::PiecewisePolynomial::
ExtrapolationPolicy::Constant;
/// Dependent variable (or variables). Row major (i.e., C)
/// ordering. Number of elements: x_.size() * host.nCols_.
std::vector<double> y_;
/// Type of fundamental table interpolant.
using Backend = ::Opm::Interp1D::
PiecewisePolynomial::Linear<Extrap>;
/// Value of dependent variable at position (row,c).
double y(const ECLPropTableRawData::SizeType nCols,
const ECLPropTableRawData::SizeType row,
const ResultColumn& c) const;
Backend interp_;
};
/// Number of result/dependent variables (== #table cols - 1).

View File

@ -0,0 +1,350 @@
/*
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/ECLPvtCommon.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <functional>
#include <ert/ecl/ecl_kw_magic.h>
namespace {
double fvfScale(const ::Opm::ECLUnits::UnitSystem& usys)
{
// B = [rVolume / sVolume(Liquid)]
return usys.reservoirVolume()
/ usys.surfaceVolumeLiquid();
}
double fvfGasScale(const ::Opm::ECLUnits::UnitSystem& usys)
{
// B = [rVolume / sVolume(Gas)]
return usys.reservoirVolume()
/ usys.surfaceVolumeGas();
}
double rsScale(const ::Opm::ECLUnits::UnitSystem& usys)
{
// Rs = [sVolume(Gas) / sVolume(Liquid)]
return usys.surfaceVolumeGas()
/ usys.surfaceVolumeLiquid();
}
double rvScale(const ::Opm::ECLUnits::UnitSystem& usys)
{
// Rv = [sVolume(Liq) / sVolume(Gas)]
return usys.surfaceVolumeLiquid()
/ usys.surfaceVolumeGas();
}
::Opm::ECLPVT::ConvertUnits::Converter
createConverterToSI(const double uscale)
{
return ::Opm::ECLPVT::ConvertUnits::Converter {
[uscale](const double q) -> double
{
return ::Opm::unit::convert::from(q, uscale);
}
};
}
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
density(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(usys.density());
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
pressure(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(usys.pressure());
}
::Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
compressibility(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(1.0 / usys.pressure());
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
disGas(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(rsScale(usys));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
vapOil(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(rvScale(usys));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvf(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(1.0 / fvfScale(usys));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfDerivPress(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/B)/dp
const auto B_scale = fvfScale(usys);
const auto P_scale = usys.pressure();
return createConverterToSI(1.0 / (B_scale * P_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/B)/dRv
const auto B_scale = fvfScale(usys);
const auto Rv_scale = rvScale(usys);
return createConverterToSI(1.0 / (B_scale * Rv_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfVisc(const ::Opm::ECLUnits::UnitSystem& usys)
{
const auto Bscale = fvfScale(usys);
const auto visc_scale = usys.viscosity();
return createConverterToSI(1.0 / (Bscale * visc_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfViscDerivPress(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/(B*mu))/dp
const auto B_scale = fvfScale(usys);
const auto P_scale = usys.pressure();
const auto mu_scale = usys.viscosity();
return createConverterToSI(1.0 / (B_scale * mu_scale * P_scale));
}
::Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfViscDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/(B*mu))/dRv
const auto B_scale = fvfScale(usys);
const auto mu_scale = usys.viscosity();
const auto Rv_scale = rvScale(usys);
return createConverterToSI(1.0 / (B_scale * mu_scale * Rv_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfGas(const ::Opm::ECLUnits::UnitSystem& usys)
{
return createConverterToSI(1.0 / fvfGasScale(usys));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfGasDerivPress(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/B)/dp
const auto B_scale = fvfGasScale(usys);
const auto P_scale = usys.pressure();
return createConverterToSI(1.0 / (B_scale * P_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfGasDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/B)/dRv
const auto B_scale = fvfGasScale(usys);
const auto Rv_scale = rvScale(usys);
return createConverterToSI(1.0 / (B_scale * Rv_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfGasVisc(const ::Opm::ECLUnits::UnitSystem& usys)
{
const auto Bscale = fvfGasScale(usys);
const auto visc_scale = usys.viscosity();
return createConverterToSI(1.0 / (Bscale * visc_scale));
}
Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfGasViscDerivPress(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/(B*mu))/dp
const auto B_scale = fvfGasScale(usys);
const auto P_scale = usys.pressure();
const auto mu_scale = usys.viscosity();
return createConverterToSI(1.0 / (B_scale * mu_scale * P_scale));
}
::Opm::ECLPVT::ConvertUnits::Converter
Opm::ECLPVT::CreateUnitConverter::ToSI::
recipFvfGasViscDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys)
{
// d(1/(B*mu))/dRv
const auto B_scale = fvfGasScale(usys);
const auto mu_scale = usys.viscosity();
const auto Rv_scale = rvScale(usys);
return createConverterToSI(1.0 / (B_scale * mu_scale * Rv_scale));
}
// =====================================================================
Opm::ECLPVT::PVDx::PVDx(ElemIt xBegin,
ElemIt xEnd,
const ConvertUnits& convert,
std::vector<ElemIt>& colIt)
: interp_(Extrap{}, xBegin, xEnd, colIt,
convert.indep, convert.column)
{}
std::vector<double>
Opm::ECLPVT::PVDx::formationVolumeFactor(const std::vector<double>& p) const
{
return this->computeQuantity(p,
[this](const EvalPt& pt) -> double
{
// 1 / (1 / B)
return 1.0 / this->fvf_recip(pt);
});
}
std::vector<double>
Opm::ECLPVT::PVDx::viscosity(const std::vector<double>& p) const
{
return this->computeQuantity(p,
[this](const EvalPt& pt) -> double
{
// (1 / B) / (1 / (B * mu)
return this->fvf_recip(pt) / this->fvf_mu_recip(pt);
});
}
Opm::FlowDiagnostics::Graph
Opm::ECLPVT::PVDx::getPvtCurve(const RawCurve curve) const
{
assert ((curve == RawCurve::FVF) ||
(curve == RawCurve::Viscosity));
const auto colID = (curve == RawCurve::FVF)
? std::size_t{0} : std::size_t{1};
auto x = this->interp_.independentVariable();
auto y = this->interp_.resultVariable(colID);
assert ((x.size() == y.size()) && "Setup Error");
// Post-process ordinates according to which curve is requested.
if (curve == RawCurve::FVF) {
// y == 1/B. Convert to proper FVF.
for (auto& yi : y) {
yi = 1.0 / yi;
}
}
else {
// y == 1/(B*mu). Extract viscosity term through the usual
// conversion formula:
//
// (1 / B) / (1 / (B*mu)).
const auto b = this->interp_.resultVariable(0); // 1/B
assert ((b.size() == y.size()) && "Setup Error");
for (auto n = y.size(), i = 0*n; i < n; ++i) {
y[i] = b[i] / y[i];
}
}
// Graph == pair<vector<double>, vector<double>>
return FlowDiagnostics::Graph { std::move(x), std::move(y) };
}
// =====================================================================
std::vector<double>
Opm::ECLPVT::surfaceMassDensity(const ECLInitFileData& init,
const ECLPhaseIndex phase)
{
const auto col = [phase]() -> std::size_t
{
// Column order: 0 <-> oil, 1 <-> water, 2 <-> gas
switch (phase) {
case ECLPhaseIndex::Aqua: return std::size_t{ 1 };
case ECLPhaseIndex::Liquid: return std::size_t{ 0 };
case ECLPhaseIndex::Vapour: return std::size_t{ 2 };
}
throw std::invalid_argument {
"Unsupported Phase ID"
};
}();
const auto& tabdims = init.keywordData<int>("TABDIMS");
const auto& tab = init.keywordData<double>("TAB");
// Subtract one to account for 1-based indices.
const auto start = tabdims[ TABDIMS_IBDENS_OFFSET_ITEM ] - 1;
const auto nreg = tabdims[ TABDIMS_NTDENS_ITEM ];
// Phase densities for 'phase' constitute 'nreg' consecutive entries
// of TAB, starting at an appropriate column offset from the table's
// 'start'.
auto rho = std::vector<double> {
&tab[ start + nreg*(col + 0) ],
&tab[ start + nreg*(col + 1) ]
};
const auto& ih = init.keywordData<int>(INTEHEAD_KW);
const auto u = ECLUnits::createUnitSystem(ih[ INTEHEAD_UNIT_INDEX ]);
const auto dens_scale = u->density();
for (auto& rho_i : rho) {
rho_i = unit::convert::from(rho_i, dens_scale);
}
return rho;
}

View File

@ -0,0 +1,768 @@
/*
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_ECLPVTCOMMON_HEADER_INCLUDED
#define OPM_ECLPVTCOMMON_HEADER_INCLUDED
#include <opm/flowdiagnostics/DerivedQuantities.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLPiecewiseLinearInterpolant.hpp>
#include <opm/utility/ECLPropTable.hpp>
#include <opm/utility/ECLTableInterpolation1D.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <algorithm>
#include <array>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <utility>
#include <type_traits>
/// \file
///
/// Facility for evaluating pressure-dependent fluid properties (formation
/// volume factor, viscosities &c) for oil or gas based on tabulated
/// descriptions as represented in an ECL result set (INIT file 'TAB'
/// vector).
namespace Opm {
class ECLInitFileData;
} // Opm
namespace Opm { namespace ECLPVT {
/// Protocol for converting raw table input data to strict SI unit
/// conventions.
struct ConvertUnits
{
/// Convenience type alias for a value transformation.
using Converter = std::function<double(const double)>;
/// How to convert the independent variate (1st column)
Converter indep;
/// How to convert the dependent variates (2nd... columns).
std::vector<Converter> column;
};
/// Collection of unit converters for PVT quantities tabulated in a
/// result set's INIT file.
struct CreateUnitConverter {
/// Convert quantities from native representations to strict SI
/// units of measure.
struct ToSI {
/// Convert quantities of type mass density (\rho) to
/// strict SI units of measure (i.e., to kg/m^3).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
density(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type pressure to strict SI units of
/// measure (i.e., to Pascal units).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
pressure(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type compressibility to strict SI
/// units of measure (i.e., to Pascal^-1 units).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
compressibility(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type dissolved gas-oil ratio (Rs) to
/// strict SI units of measure (i.e., to Sm^3/Sm^3).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
disGas(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type vaporised oil-gas ratio (Rv) to
/// strict SI units of measure (i.e., to Sm^3/Sm^3).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
vapOil(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type reciprocal formation volume
/// factor (1/B) to strict SI units of measure (i.e., to
/// Sm^3/Rm^3).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvf(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal
/// formation volume factor (1/B) with respect to fluid pressure
/// to strict SI units of measure (i.e., to Sm^3/(Rm^3 * Pa)).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfDerivPress(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal
/// formation volume factor (1/B) with respect to vaporised
/// oil-gas ratio to strict SI units of measure (i.e., to
/// Sm^3/(Rm^3 * (Sm^3 / Sm^3))).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type reciprocal product of formation
/// volume factor and phase viscosity (1/(B * mu)) to strict SI
/// units of measure (i.e., to Sm^3/(Rm^3 * Pa*s)).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfVisc(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal product
/// of formation volume factor and phase viscosity (1/(B * mu))
/// with respect to fluid pressure to strict SI units of measure
/// (i.e., to Sm^3/(Rm^3 * Pa*s * Pa)).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfViscDerivPress(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal product
/// of formation volume factor and phase viscosity (1/(B * mu))
/// with respect to vaporised oil-gas ratio to strict SI units
/// of measure (i.e., to Sm^3/(Rm^3 * Pa*s * (Sm^3/Sm^3))).
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfViscDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type reciprocal formation volume
/// factor (1/B) to strict SI units of measure (i.e., to
/// Sm^3/Rm^3).
///
/// Specialisation for Gas.
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfGas(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal
/// formation volume factor (1/B) with respect to fluid pressure
/// to strict SI units of measure (i.e., to Sm^3/(Rm^3 * Pa)).
///
/// Specialisation for Gas.
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfGasDerivPress(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal
/// formation volume factor (1/B) with respect to vaporised
/// oil-gas ratio to strict SI units of measure (i.e., to
/// Sm^3/(Rm^3 * (Sm^3 / Sm^3))).
///
/// Specialisation for Gas.
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfGasDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert quantities of type reciprocal product of formation
/// volume factor and phase viscosity (1/(B * mu)) to strict SI
/// units of measure (i.e., to Sm^3/(Rm^3 * Pa*s)).
///
/// Specialisation for Gas.
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfGasVisc(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal product
/// of formation volume factor and phase viscosity (1/(B * mu))
/// with respect to fluid pressure to strict SI units of measure
/// (i.e., to Sm^3/(Rm^3 * Pa*s * Pa)).
///
/// Specialisation for Gas.
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfGasViscDerivPress(const ::Opm::ECLUnits::UnitSystem& usys);
/// Convert derivatives of quantities of type reciprocal product
/// of formation volume factor and phase viscosity (1/(B * mu))
/// with respect to vaporised oil-gas ratio to strict SI units
/// of measure (i.e., to Sm^3/(Rm^3 * Pa*s * (Sm^3/Sm^3))).
///
/// Specialisation for Gas.
///
/// \param[in] usys Native unit system for particular result
/// set.
///
/// \return Value transformation function affecting requisite
/// unit conversion.
static ConvertUnits::Converter
recipFvfGasViscDerivVapOil(const ::Opm::ECLUnits::UnitSystem& usys);
};
};
enum class RawCurve {
/// Formation volume factor (B_\alpha)
FVF,
/// Viscosity
Viscosity,
};
template <std::size_t N>
class DenseVector {
public:
explicit DenseVector(const std::array<double, N>& other)
: x_(other)
{}
DenseVector& operator+=(const DenseVector& rhs)
{
std::transform(std::begin(this->x_),
std::end (this->x_),
std::begin(rhs .x_),
std::begin(this->x_),
std::plus<double>());
return *this;
}
DenseVector& operator-=(const DenseVector& rhs)
{
std::transform(std::begin(this->x_),
std::end (this->x_),
std::begin(rhs .x_),
std::begin(this->x_),
std::minus<double>());
return *this;
}
DenseVector& operator*=(const double rhs)
{
std::transform(std::begin(this->x_),
std::end (this->x_),
std::begin(this->x_),
[rhs](const double xi)
{
return rhs * xi;
});
return *this;
}
DenseVector& operator/=(const double rhs)
{
std::transform(std::begin(this->x_),
std::end (this->x_),
std::begin(this->x_),
[rhs](const double xi)
{
return xi / rhs;
});
return *this;
}
const std::array<double, N>& array() const
{
return this->x_;
}
private:
std::array<double, N> x_;
};
template <std::size_t N>
DenseVector<N> operator/(DenseVector<N> v, const double a)
{
return v *= 1.0 / a;
}
template <std::size_t N>
DenseVector<N> operator*(const double a, DenseVector<N> v)
{
return v *= a;
}
template <std::size_t N>
DenseVector<N> operator*(DenseVector<N> v, const double a)
{
return v *= a;
}
template <std::size_t N>
DenseVector<N> operator+(DenseVector<N> u, const DenseVector<N>& v)
{
return u += v;
}
template <std::size_t N>
DenseVector<N> operator-(DenseVector<N> u, const DenseVector<N>& v)
{
return u -= v;
}
/// Evaluate pressure-dependent properties (formation volume factor,
/// viscosity &c) for dead oil (PVDO) or dry gas (PVDG) from tabulated
/// functions as represented in an ECL result set (ECLInitData).
class PVDx
{
public:
/// Convenience type alias.
using ElemIt = ECLPropTableRawData::ElementIterator;
/// Constructor.
///
/// \param[in] xBegin Starting position of range of independent
/// variable (phase pressure).
///
/// \param[in] xEnd One past the end of range of independent
/// variable. Must be reachable from \p xBegin.
///
/// \param[in,out] colIt Column iterators that reference the
/// dependent variables (reciprocal FVF &c). Should be size 4 to
/// represent the FVF, the viscosity and the derivatives with
/// respect to phase pressure. On input, positioned at the
/// beginning of a single table's dependent variables columns.
/// On output, advanced across \code std::distance(xBegin, xEnd)
/// \endcode rows/entries.
PVDx(ElemIt xBegin,
ElemIt xEnd,
const ConvertUnits& convert,
std::vector<ElemIt>& colIt);
/// Evaluate the phase FVF in selection of pressure points.
///
/// \param[in] p Set of phase pressure points.
///
/// \return Phase Formation volume factors for each pressure point.
std::vector<double>
formationVolumeFactor(const std::vector<double>& p) const;
/// Evaluate the phase viscosity in selection of pressure points.
///
/// \param[in] p Set of phase pressure points.
///
/// \return Phase viscosity for each pressure point.
std::vector<double>
viscosity(const std::vector<double>& p) const;
/// Retrieve 2D graph representation PVT property function.
///
/// \param[in] curve PVT property curve descriptor
///
/// \return 2D graph for PVT property curve identified by
/// requests represented by \p func.
///
/// Example: Retrieve formation volume factor curve.
///
/// \code
/// const auto graph =
/// pvdx.getPvtCurve(ECLPVT::RawCurve::FVF);
/// \endcode
FlowDiagnostics::Graph getPvtCurve(const RawCurve curve) const;
private:
/// Extrapolation policy for property evaluator/interpolant.
using Extrap = ::Opm::Interp1D::PiecewisePolynomial::
ExtrapolationPolicy::Linearly;
/// Type of fundamental table interpolant.
using Backend = ::Opm::Interp1D::PiecewisePolynomial::Linear<Extrap>;
/// Convenience type alias representing the interpolant's evaluation
/// point conventions.
using EvalPt = std::decay<
decltype(std::declval<Backend>().classifyPoint(0.0))
>::type;
/// Interpolant for table of
///
/// [1/B, 1/(B*mu), d(1/B)/dp, d(1/(B*mu))/dp]
///
/// versus "pressure" p--typically phase pressure for oil (Po) or
/// gas (Pg).
Backend interp_;
/// Translate pressure value to evaluation point and identify
/// relevant extrapolation case if needed.
EvalPt getInterpPoint(const double p) const
{
return this->interp_.classifyPoint(p);
}
/// Interpolate reciprocal FVF at evaluation point.
double fvf_recip(const EvalPt& pt) const
{
const auto col = std::size_t{0};
return this->interp_.evaluate(col, pt);
}
/// Interpolate reciprocal product of FVF and viscosity at
/// evaluation point.
double fvf_mu_recip(const EvalPt& pt) const
{
const auto col = std::size_t{1};
return this->interp_.evaluate(col, pt);
}
/// Compute a dynamic quantity such as the FVF or viscosity for a
/// selection of pressure values.
template <class EvalDynamicQuant>
std::vector<double>
computeQuantity(const std::vector<double>& p,
EvalDynamicQuant&& eval) const
{
auto result = std::vector<double>{};
result.reserve(p.size());
for (const auto& pi : p) {
result.push_back(eval(this->getInterpPoint(pi)));
}
return result;
}
};
/// Evaluate pressure-dependent properties (formation volume factor,
/// viscosity &c) for live oil (PVTO) or wet gas (PVTG) from tabulated
/// functions as represented in an ECL result set (ECLInitData).
template <class SubtableInterpolant>
class PVTx
{
public:
PVTx(std::vector<double> key,
std::vector<SubtableInterpolant> propInterp)
: key_ (std::move(key))
, propInterp_(std::move(propInterp))
{
if (this->key_.size() != this->propInterp_.size()) {
throw std::invalid_argument {
"Size of Key Table Does Not Match "
"Number of Sub-Table Interpolants"
};
}
if (this->key_.size() < 2) {
throw std::invalid_argument {
"Mixing-Dependent Property Evaluator "
"Must Have At Least Two Inner Tables"
};
}
}
struct PrimaryKey
{
const std::vector<double>& data;
};
struct InnerVariate
{
const std::vector<double>& data;
};
std::vector<double>
formationVolumeFactor(const PrimaryKey& key,
const InnerVariate& x) const
{
const auto col = std::size_t{0};
return this->computeQuantity(key, x,
[this, col](const std::size_t curve,
const InnerEvalPoint& pt) -> double
{ // IFunc: Interpolate 1 / B.
return this->propInterp_[curve].evaluate(col, pt);
},
[](const double recipFvF) -> double
{
// OFunc: Convert reciprocal FvF to ordinary FvF.
return 1.0 / recipFvF;
});
}
std::vector<double>
viscosity(const PrimaryKey& key,
const InnerVariate& x) const
{
return this->computeQuantity(key, x,
[this](const std::size_t curve,
const InnerEvalPoint& pt) -> DenseVector<2>
{
// IFunc: Interpolate 1/B and 1/(B*mu)
const auto& I = this->propInterp_[curve];
const auto fvf_recip = I.evaluate(0, pt);
const auto fvf_mu_recip = I.evaluate(1, pt);
// { (1 / B) , (1 / (B*mu)) }
return DenseVector<2>{
std::array<double,2>{
{ fvf_recip, fvf_mu_recip }
}
};
},
[](const DenseVector<2>& recipFvFVisc) -> double
{
// OFunc: Compute viscosity as
// (1 / B) / (1 / B*mu)
const auto& v = recipFvFVisc.array();
return v[0] / v[1];
});
}
private:
using InnerEvalPoint = typename std::decay<
decltype(std::declval<SubtableInterpolant>().classifyPoint(0.0))
>::type;
using OuterInterpPoint =
::Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint;
struct InnerInterpPoint {
InnerEvalPoint left;
InnerEvalPoint right;
};
std::vector<double> key_;
std::vector<SubtableInterpolant> propInterp_;
InnerInterpPoint getInterpPoint(const std::size_t i,
const double x) const
{
assert ((i + 1) < this->propInterp_.size());
return {
this->propInterp_[i + 0].classifyPoint(x),
this->propInterp_[i + 1].classifyPoint(x)
};
}
template <class Function>
auto interpolate(Function&& func,
const OuterInterpPoint& outer,
const double x) const
-> decltype(func(outer.interval, std::declval<InnerEvalPoint>()))
{
assert (outer.cat == ::Opm::Interp1D::PointCategory::InRange);
const auto pt =
this->getInterpPoint(outer.interval, x);
const auto yLeft = func(outer.interval + 0, pt.left);
const auto yRight = func(outer.interval + 1, pt.right);
const auto t = outer.t / (this->key_[outer.interval + 1] -
this->key_[outer.interval + 0]);
// t == 0 => yLeft, t == 1 => yRight
return t*yRight + (1.0 - t)*yLeft;
}
template <class Function>
auto extrapLeft(Function&& func,
const OuterInterpPoint& outer,
const double x) const
-> decltype(func(outer.interval, std::declval<InnerEvalPoint>()))
{
assert (outer.cat == ::Opm::Interp1D::PointCategory::LeftOfRange);
assert (outer.interval == 0*this->key_.size());
const auto pt =
this->getInterpPoint(outer.interval, x);
const auto yLeft = func(0, pt.left);
const auto yRight = func(1, pt.right);
const auto dydk =
(yRight - yLeft) / (this->key_[1] - this->key_[0]);
return yLeft + outer.t*dydk;
}
template <class Function>
auto extrapRight(Function&& func,
const OuterInterpPoint& outer,
const double x) const
-> decltype(func(outer.interval, std::declval<InnerEvalPoint>()))
{
const auto nIntervals = this->key_.size() - 1;
assert (outer.cat == ::Opm::Interp1D::PointCategory::RightOfRange);
assert (outer.interval == nIntervals);
const auto pt = this->getInterpPoint(nIntervals - 1, x);
const auto yLeft = func(nIntervals - 1, pt.left);
const auto yRight = func(nIntervals - 0, pt.right);
const auto dydk =
(yRight - yLeft) / (this->key_[nIntervals - 0] -
this->key_[nIntervals - 1]);
return yRight + outer.t*dydk;
}
template <class Function>
auto evaluate(const double key,
const double x,
Function&& func) const
-> decltype(func(std::declval<OuterInterpPoint>().interval,
std::declval<InnerEvalPoint>()))
{
const auto outer = ::Opm::Interp1D::PiecewisePolynomial::
LocalInterpPoint::identify(this->key_, key);
switch (outer.cat) {
case ::Opm::Interp1D::PointCategory::InRange:
return this->interpolate(std::forward<Function>(func),
outer, x);
case ::Opm::Interp1D::PointCategory::LeftOfRange:
return this->extrapLeft(std::forward<Function>(func),
outer, x);
case ::Opm::Interp1D::PointCategory::RightOfRange:
return this->extrapRight(std::forward<Function>(func),
outer, x);
}
throw std::logic_error {
"Outer/Primary Key Cannot Be Classified"
};
}
template <class InnerFunction, class OuterFunction>
std::vector<double>
computeQuantity(const PrimaryKey& key,
const InnerVariate& x,
InnerFunction&& ifunc,
OuterFunction ofunc) const
{
auto result = std::vector<double>{};
const auto nVals = key.data.size();
if (x.data.size() != nVals) {
throw std::invalid_argument {
"Number of Inner Sampling Points Does Not Match "
"Number of Outer Sampling Points"
};
}
result.reserve(nVals);
for (auto i = 0*nVals; i < nVals; ++i) {
const auto q =
this->evaluate(key.data[i], x.data[i],
std::forward<InnerFunction>(ifunc));
result.push_back(ofunc(q));
}
return result;
}
};
/// Extract component mass density at surface conditions.
///
/// \param[in] init ECL result set INIT file representation.
///
/// \param[in] phase
std::vector<double>
surfaceMassDensity(const ECLInitFileData& init,
const ECLPhaseIndex phase);
}} // Opm::ECLPVT
#endif // OPM_ECLPVTCOMMON_HEADER_INCLUDED

View File

@ -0,0 +1,68 @@
#include <opm/utility/ECLPvtCurveCollection.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <vector>
namespace {
std::vector<int>
pvtnumVector(const ::Opm::ECLGraph& G,
const ::Opm::ECLInitFileData& init)
{
auto pvtnum = G.rawLinearisedCellData<int>(init, "PVTNUM");
if (pvtnum.empty()) {
// PVTNUM missing in one or more of the grids managed by 'G'.
// Put all cells in PVTNUM region 1.
pvtnum.assign(G.numCells(), 1);
}
return pvtnum;
}
}
Opm::ECLPVT::ECLPvtCurveCollection::
ECLPvtCurveCollection(const ECLGraph& G,
const ECLInitFileData& init)
: pvtnum_(pvtnumVector(G, init))
, gas_ (CreateGasPVTInterpolant::fromECLOutput(init)) // u_p<> -> s_p<>
, oil_ (CreateOilPVTInterpolant::fromECLOutput(init)) // u_p<> -> s_p<>
{}
Opm::FlowDiagnostics::Graph
Opm::ECLPVT::ECLPvtCurveCollection::
getPvtCurve(const RawCurve curve,
const ECLPhaseIndex phase,
const int activeCell) const
{
if (phase == ECLPhaseIndex::Aqua) {
// Not supported at this time.
// Return empty.
return FlowDiagnostics::Graph{};
}
if (static_cast<decltype(this->pvtnum_.size())>(activeCell)
>= this->pvtnum_.size())
{
// Active cell index out of bounds. Return empty.
return FlowDiagnostics::Graph{};
}
// PVTNUM is traditional one-based region identifier. Subtract one to
// form valid index into std::vector<>s.
const auto regID = this->pvtnum_[activeCell] - 1;
if (phase == ECLPhaseIndex::Liquid) {
// return this->oil_->getPvtCurve(curve, regID);
return FlowDiagnostics::Graph{};
}
if (this->gas_) {
return this->gas_->getPvtCurve(curve, regID);
}
else {
// Result set does not provide tabulated gas properties. Return
// empty.
return FlowDiagnostics::Graph{};
}
}

View File

@ -0,0 +1,69 @@
/*
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_ECLPVTCURVECOLLECTION_HEADER_INCLUDED
#define OPM_ECLPVTCURVECOLLECTION_HEADER_INCLUDED
#include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <opm/utility/ECLPvtGas.hpp>
#include <opm/utility/ECLPvtOil.hpp>
#include <memory>
#include <vector>
/// \file
///
/// Facility for evaluating pressure-dependent fluid properties (formation
/// volume factor, viscosities &c) for oil or gas based on tabulated
/// descriptions as represented in an ECL result set (INIT file 'TAB'
/// vector).
namespace Opm {
class ECLInitFileData;
} // Opm
namespace Opm { namespace ECLPVT {
class ECLPvtCurveCollection
{
public:
ECLPvtCurveCollection(const ECLGraph& G,
const ECLInitFileData& init);
FlowDiagnostics::Graph
getPvtCurve(const RawCurve curve,
const ECLPhaseIndex phase,
const int activeCell) const;
private:
/// Forward map: Cell -> PVT Region ID
std::vector<int> pvtnum_;
/// Gas PVT property evaluator.
std::shared_ptr<Gas> gas_; // shared => default special member funcs.
/// Oil PVT property evaluator.
std::shared_ptr<Oil> oil_;
};
}} // Opm::ECLPVT
#endif // OPM_ECLPVTCURVECOLLECTION_HEADER_INCLUDED

View File

@ -0,0 +1,606 @@
/*
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/ECLPvtGas.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLPropTable.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <cassert>
#include <cmath>
#include <exception>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <ert/ecl/ecl_kw_magic.h>
namespace {
::Opm::ECLPVT::ConvertUnits
createDryGasUnitConverter(const int usys)
{
using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI;
const auto u = ::Opm::ECLUnits::createUnitSystem(usys);
// [ Pg, 1/B, 1/(B*mu), d(1/B)/dP, d(1/(B*mu))/dP ]
return ::Opm::ECLPVT::ConvertUnits {
ToSI::pressure(*u),
{
ToSI::recipFvfGas(*u),
ToSI::recipFvfGasVisc(*u),
ToSI::recipFvfGasDerivPress(*u),
ToSI::recipFvfGasViscDerivPress(*u)
}
};
}
std::pair< ::Opm::ECLPVT::ConvertUnits::Converter,
::Opm::ECLPVT::ConvertUnits>
wetGasUnitConverter(const int usys)
{
using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI;
const auto u = ::Opm::ECLUnits::createUnitSystem(usys);
// Key = Pg
// Table = [ Rv, 1/B, 1/(B*mu), d(1/B)/dRv, d(1/(B*mu))/dRv ]
auto cvrtTable = ::Opm::ECLPVT::ConvertUnits {
ToSI::vapOil(*u),
{
ToSI::recipFvfGas(*u),
ToSI::recipFvfGasVisc(*u),
ToSI::recipFvfGasDerivVapOil(*u),
ToSI::recipFvfGasViscDerivVapOil(*u)
}
};
return std::make_pair(ToSI::pressure(*u),
std::move(cvrtTable));
}
}
// Enable runtime selection of dry or wet gas functions.
class PVxGBase
{
public:
virtual std::vector<double>
formationVolumeFactor(const std::vector<double>& rv,
const std::vector<double>& pg) const = 0;
virtual std::vector<double>
viscosity(const std::vector<double>& rv,
const std::vector<double>& pg) const = 0;
virtual Opm::FlowDiagnostics::Graph
getPvtCurve(const Opm::ECLPVT::RawCurve curve) const = 0;
virtual std::unique_ptr<PVxGBase> clone() const = 0;
};
// =====================================================================
class DryGas : public PVxGBase
{
public:
using ElemIt = ::Opm::ECLPVT::PVDx::ElemIt;
using ConvertUnits = ::Opm::ECLPVT::ConvertUnits;
DryGas(ElemIt xBegin,
ElemIt xEnd,
const ConvertUnits& convert,
std::vector<ElemIt>& colIt)
: interpolant_(xBegin, xEnd, convert, colIt)
{}
virtual std::vector<double>
formationVolumeFactor(const std::vector<double>& /* rv */,
const std::vector<double>& pg) const override
{
return this->interpolant_.formationVolumeFactor(pg);
}
virtual std::vector<double>
viscosity(const std::vector<double>& /* rv */,
const std::vector<double>& pg) const override
{
return this->interpolant_.viscosity(pg);
}
virtual Opm::FlowDiagnostics::Graph
getPvtCurve(const Opm::ECLPVT::RawCurve curve) const override
{
return this->interpolant_.getPvtCurve(curve);
}
virtual std::unique_ptr<PVxGBase> clone() const override
{
return std::unique_ptr<PVxGBase>(new DryGas(*this));
}
private:
::Opm::ECLPVT::PVDx interpolant_;
};
// =====================================================================
class WetGas : public PVxGBase
{
public:
using Extrap = ::Opm::Interp1D::PiecewisePolynomial::
ExtrapolationPolicy::Linearly;
using SubtableInterpolant = ::Opm::Interp1D::PiecewisePolynomial::
Linear<Extrap, /* IsAscendingRange = */ false>;
WetGas(std::vector<double> key,
std::vector<SubtableInterpolant> propInterp)
: interp_(std::move(key), std::move(propInterp))
{}
virtual std::vector<double>
formationVolumeFactor(const std::vector<double>& rv,
const std::vector<double>& pg) const override
{
// PKey Inner C0 C1 C2 C3
// Pg Rv 1/B 1/(B*mu) d(1/B)/dRv d(1/(B*mu))/dRv
// : : : : :
const auto key = TableInterpolant::PrimaryKey { pg };
const auto x = TableInterpolant::InnerVariate { rv };
return this->interp_.formationVolumeFactor(key, x);
}
virtual std::vector<double>
viscosity(const std::vector<double>& rv,
const std::vector<double>& pg) const override
{
// PKey Inner C0 C1 C2 C3
// Pg Rv 1/B 1/(B*mu) d(1/B)/dRv d(1/(B*mu))/dRv
// : : : : :
const auto key = TableInterpolant::PrimaryKey { pg };
const auto x = TableInterpolant::InnerVariate { rv };
return this->interp_.viscosity(key, x);
}
virtual Opm::FlowDiagnostics::Graph
getPvtCurve(const Opm::ECLPVT::RawCurve /* curve */) const override
{
throw std::runtime_error {
"Property Evaluator for Wet Gas Does not "
"Support Retrieving Raw Curves (Blame BSKA)"
};
}
virtual std::unique_ptr<PVxGBase> clone() const override
{
return std::unique_ptr<PVxGBase>(new WetGas(*this));
}
private:
using TableInterpolant = ::Opm::ECLPVT::PVTx<SubtableInterpolant>;
TableInterpolant interp_;
};
// #####################################################################
namespace {
std::vector<std::unique_ptr<PVxGBase>>
createDryGas(const ::Opm::ECLPropTableRawData& raw,
const int usys)
{
using PVTInterp = std::unique_ptr<PVxGBase>;
using ElmIt = ::Opm::ECLPropTableRawData::ElementIterator;
assert ((raw.numPrimary == 1) &&
"Can't Create Dry Gas Function From Wet Gas Table");
const auto cvrt = createDryGasUnitConverter(usys);
return ::Opm::MakeInterpolants<PVTInterp>::fromRawData(raw,
[&cvrt](ElmIt xBegin, ElmIt xEnd, std::vector<ElmIt>& colIt)
{
return PVTInterp{ new DryGas(xBegin, xEnd, cvrt, colIt) };
});
}
std::vector<double>
extractPrimaryKey(const ::Opm::ECLPropTableRawData& raw,
const ::Opm::ECLPropTableRawData::SizeType t,
const ::Opm::ECLPVT::ConvertUnits::Converter& cvrtKey)
{
auto key = std::vector<double>{};
key.reserve(raw.numPrimary);
for (auto begin = std::begin(raw.primaryKey) + (t + 0)*raw.numPrimary,
end = begin + raw.numPrimary;
begin != end; ++begin)
{
if (std::abs(*begin) < 1.0e20) {
key.push_back(cvrtKey(*begin));
}
}
return key;
}
std::vector<std::unique_ptr<PVxGBase>>
createWetGas(const ::Opm::ECLPropTableRawData& raw,
const int usys)
{
auto ret = std::vector<std::unique_ptr<PVxGBase>>{};
ret.reserve(raw.numTables);
using Extrap = WetGas::Extrap;
using StI = WetGas::SubtableInterpolant;
using ElemIt = ::Opm::ECLPropTableRawData::ElementIterator;
const auto cvrt = wetGasUnitConverter(usys);
auto sti = ::Opm::MakeInterpolants<StI>::fromRawData(raw,
[&cvrt](ElemIt xBegin,
ElemIt xEnd,
std::vector<ElemIt>& colIt) -> StI
{
try {
return StI(Extrap{}, xBegin, xEnd, colIt,
cvrt.second.indep,
cvrt.second.column);
}
catch (const std::invalid_argument&) {
// No valid nodes. Return invalid.
return StI(Extrap{});
}
});
for (auto t = 0*raw.numTables;
t < raw.numTables; ++t)
{
auto key = extractPrimaryKey(raw, t, cvrt.first);
const auto begin = (t + 0)*raw.numPrimary;
const auto end = begin + key.size();
ret.emplace_back(new WetGas(std::move(key),
{
std::make_move_iterator(std::begin(sti) + begin),
std::make_move_iterator(std::begin(sti) + end)
}));
}
return ret;
}
std::vector<std::unique_ptr<PVxGBase>>
createPVTFunction(const ::Opm::ECLPropTableRawData& raw,
const int usys)
{
if (raw.numPrimary == 0) {
// Malformed Gas PVT table.
throw std::invalid_argument {
"Gas PVT Table Without Primary Lookup Key"
};
}
if (raw.numCols != 5) {
throw std::invalid_argument {
"PVT Table for Gas Must Have Five Columns"
};
}
if (raw.primaryKey.size() != (raw.numPrimary * raw.numTables)) {
throw std::invalid_argument {
"Size Mismatch in Pressure Nodes of PVT Table for Gas"
};
}
if (raw.data.size() !=
(raw.numPrimary * raw.numRows * raw.numCols * raw.numTables))
{
throw std::invalid_argument {
"Size Mismatch in Condensed Table Data "
"of PVT Table for Gas"
};
}
if (raw.numPrimary == 1) {
return createDryGas(raw, usys);
}
return createWetGas(raw, usys);
}
std::vector<std::unique_ptr<PVxGBase>>
clone(const std::vector<std::unique_ptr<PVxGBase>>& src)
{
auto dest = std::vector<std::unique_ptr<PVxGBase>>{};
dest.reserve(src.size());
for (const auto& p : src) {
dest.push_back(p->clone());
}
return dest;
}
}
// #####################################################################
// #####################################################################
// =====================================================================
// Class ECLPVT::Gas::Impl
// ---------------------------------------------------------------------
class Opm::ECLPVT::Gas::Impl
{
private:
using EvalPtr = std::unique_ptr<PVxGBase>;
public:
Impl(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS);
Impl(const Impl& rhs);
Impl(Impl&& rhs);
Impl& operator=(const Impl& rhs);
Impl& operator=(Impl&& rhs);
using RegIdx = std::vector<EvalPtr>::size_type;
std::vector<double>
formationVolumeFactor(const RegIdx region,
const std::vector<double>& rv,
const std::vector<double>& pg) const;
std::vector<double>
viscosity(const RegIdx region,
const std::vector<double>& rv,
const std::vector<double>& pg) const;
double surfaceMassDensity(const RegIdx region) const
{
this->validateRegIdx(region);
return this->rhoS_[region];
}
FlowDiagnostics::Graph
getPvtCurve(const RegIdx region,
const RawCurve curve) const;
private:
std::vector<EvalPtr> eval_;
std::vector<double> rhoS_;
void validateRegIdx(const RegIdx region) const;
};
Opm::ECLPVT::Gas::Impl::
Impl(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS)
: eval_(createPVTFunction(raw, usys))
, rhoS_(std::move(rhoS))
{}
Opm::ECLPVT::Gas::Impl::Impl(const Impl& rhs)
: eval_(clone(rhs.eval_))
, rhoS_(rhs.rhoS_)
{}
Opm::ECLPVT::Gas::Impl::Impl(Impl&& rhs)
: eval_(std::move(rhs.eval_))
, rhoS_(std::move(rhs.rhoS_))
{}
Opm::ECLPVT::Gas::Impl&
Opm::ECLPVT::Gas::Impl::operator=(const Impl& rhs)
{
this->eval_ = clone(rhs.eval_);
this->rhoS_ = rhs.rhoS_;
return *this;
}
Opm::ECLPVT::Gas::Impl&
Opm::ECLPVT::Gas::Impl::operator=(Impl&& rhs)
{
this->eval_ = std::move(rhs.eval_);
this->rhoS_ = std::move(rhs.rhoS_);
return *this;
}
std::vector<double>
Opm::ECLPVT::Gas::Impl::
formationVolumeFactor(const RegIdx region,
const std::vector<double>& rv,
const std::vector<double>& pg) const
{
this->validateRegIdx(region);
return this->eval_[region]->formationVolumeFactor(rv, pg);
}
std::vector<double>
Opm::ECLPVT::Gas::Impl::
viscosity(const RegIdx region,
const std::vector<double>& rv,
const std::vector<double>& pg) const
{
this->validateRegIdx(region);
return this->eval_[region]->viscosity(rv, pg);
}
Opm::FlowDiagnostics::Graph
Opm::ECLPVT::Gas::Impl::
getPvtCurve(const RegIdx region,
const RawCurve curve) const
{
this->validateRegIdx(region);
return this->eval_[region]->getPvtCurve(curve);
}
void
Opm::ECLPVT::Gas::Impl::validateRegIdx(const RegIdx region) const
{
if (region >= this->eval_.size()) {
throw std::invalid_argument {
"Region Index " +
std::to_string(region) +
" Outside Valid Range (0 .. " +
std::to_string(this->eval_.size() - 1) + ')'
};
}
}
// ======================================================================
// Class ECLPVT::Gas
// ----------------------------------------------------------------------
Opm::ECLPVT::Gas::Gas(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS)
: pImpl_(new Impl(raw, usys, std::move(rhoS)))
{}
Opm::ECLPVT::Gas::~Gas()
{}
Opm::ECLPVT::Gas::Gas(const Gas& rhs)
: pImpl_(new Impl(*rhs.pImpl_))
{}
Opm::ECLPVT::Gas::Gas(Gas&& rhs)
: pImpl_(std::move(rhs.pImpl_))
{}
Opm::ECLPVT::Gas&
Opm::ECLPVT::Gas::operator=(const Gas& rhs)
{
this->pImpl_.reset(new Impl(*rhs.pImpl_));
return *this;
}
Opm::ECLPVT::Gas&
Opm::ECLPVT::Gas::operator=(Gas&& rhs)
{
this->pImpl_ = std::move(rhs.pImpl_);
return *this;
}
std::vector<double>
Opm::ECLPVT::Gas::
formationVolumeFactor(const int region,
const VaporizedOil& rv,
const GasPressure& pg) const
{
return this->pImpl_->formationVolumeFactor(region, rv.data, pg.data);
}
std::vector<double>
Opm::ECLPVT::Gas::
viscosity(const int region,
const VaporizedOil& rv,
const GasPressure& pg) const
{
return this->pImpl_->viscosity(region, rv.data, pg.data);
}
double Opm::ECLPVT::Gas::surfaceMassDensity(const int region) const
{
return this->pImpl_->surfaceMassDensity(region);
}
Opm::FlowDiagnostics::Graph
Opm::ECLPVT::Gas::
getPvtCurve(const RawCurve curve, const int region) const
{
return this->pImpl_->getPvtCurve(region, curve);
}
// =====================================================================
std::unique_ptr<Opm::ECLPVT::Gas>
Opm::ECLPVT::CreateGasPVTInterpolant::
fromECLOutput(const ECLInitFileData& init)
{
using GPtr = ::std::unique_ptr<Opm::ECLPVT::Gas>;
const auto& ih = init.keywordData<int>(INTEHEAD_KW);
const auto iphs = static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]);
if ((iphs & (1u << 2)) == 0) {
// Gas is not an active phase.
// Return sentinel (null) pointer.
return GPtr{};
}
auto raw = ::Opm::ECLPropTableRawData{};
const auto& tabdims = init.keywordData<int>("TABDIMS");
const auto& tab = init.keywordData<double>("TAB");
raw.numPrimary = tabdims[ TABDIMS_NRPVTG_ITEM ]; // #Composition nodes
raw.numRows = tabdims[ TABDIMS_NPPVTG_ITEM ]; // #Rv (or Pg) nodes
raw.numCols = 5; // [ Rv, 1/B, 1/(B*mu), d(1/B)/dRv, d(1/(B*mu))/dRv ]
raw.numTables = tabdims[ TABDIMS_NTPVTG_ITEM ]; // # PVTG tables
// Extract Primary Key (Pg)
{
const auto nTabElem = raw.numPrimary * raw.numTables;
// Subtract one to account for 1-based indices.
const auto start = tabdims[ TABDIMS_JBPVTG_OFFSET_ITEM ] - 1;
raw.primaryKey.assign(&tab[start], &tab[start] + nTabElem);
}
// Extract Full Table
{
const auto nTabElem =
raw.numPrimary * raw.numRows * raw.numCols * raw.numTables;
// Subtract one to account for 1-based indices.
const auto start = tabdims[ TABDIMS_IBPVTG_OFFSET_ITEM ] - 1;
raw.data.assign(&tab[start], &tab[start] + nTabElem);
}
auto rhoS = surfaceMassDensity(init, ECLPhaseIndex::Vapour);
return GPtr{
new Gas(raw, ih[ INTEHEAD_UNIT_INDEX ], std::move(rhoS))
};
}

View File

@ -0,0 +1,200 @@
/*
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_ECLPVTGAS_HEADER_INCLUDED
#define OPM_ECLPVTGAS_HEADER_INCLUDED
#include <opm/flowdiagnostics/DerivedQuantities.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <memory>
#include <vector>
// Forward declarations
namespace Opm {
struct ECLPropTableRawData;
class ECLInitFileData;
} // Opm
namespace Opm { namespace ECLPVT {
/// Interpolant for Basic Gas PVT Relations.
class Gas
{
public:
/// Constructor.
///
/// \param[in] raw Raw tabulated data. Must correspond to the PVTG
/// vector of an ECL INIT file..
///
/// \param[in] usys Unit system convention of the result set from
/// which \p raw was extracted. Must correspond to item 3 of the
/// INTEHEAD keyword in the INIT file.
///
/// \param[in] rhoS Mass density of gas at surface conditions.
/// Typically computed by \code ECLPVT::surfaceMassDensity()
/// \endcode.
Gas(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS);
/// Destructor.
~Gas();
/// Copy constructor.
///
/// \param[in] rhs Existing interpolant for Gas PVT relations.
Gas(const Gas& rhs);
/// Move constructor.
///
/// Subsumes the implementation of an existing Gas PVT relation
/// interpolant.
///
/// \param[in] rhs Existing Gas PVT relation interpolant. Does not
/// have a valid implementation when the constructor completes.
Gas(Gas&& rhs);
/// Assignment operator
///
/// \param[in] rhs Existing Gas PVT relation interpolant.
///
/// \return \code *this \endcode.
Gas& operator=(const Gas& rhs);
/// Move assignment operator.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing Gas PVT relation interpolant. Does not
/// have a valid implementation when the constructor completes.
///
/// \return \code *this \endcode.
Gas& operator=(Gas&& rhs);
/// Representation of the Vaporised Oil-Gas Ratio (Rv).
///
/// Mostly as a convenience to disambiguate the client interface.
struct VaporizedOil
{
/// Vaporised oil-gas ratio data for all sampling points.
std::vector<double> data;
};
/// Representation of Gas Phase Pressure (Pg).
///
/// Mostly as a convenience to disambiguate the client interface.
struct GasPressure
{
/// Gas phase pressure for all sampling points.
std::vector<double> data;
};
/// Compute the gas phase formation volume factor in a single region.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \param[in] rv Vaporised oil-gas ratio. Unused in the case of a
/// dry gas model. Size must match number of elements in \code
/// pg.data \endcode in the case of wet gas model. Strict SI
/// units of measurement.
///
/// \param[in] pg Gas phase pressure. Strict SI units of measurement.
///
/// \return Gas phase formation volume factor. Size equal to number
/// of elements in \code po.data \endcode.
std::vector<double>
formationVolumeFactor(const int region,
const VaporizedOil& rv,
const GasPressure& pg) const;
/// Compute the gas phase fluid viscosity in a single region.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \param[in] rv Vaporised oil-gas ratio. Unused in the case of a
/// dry gas model. Size must match number of elements in \code
/// pg.data \endcode in the case of wet gas model. Strict SI
/// units of measurement.
///
/// \param[in] pg Gas phase pressure. Strict SI units of measurement.
///
/// \return Gas phase fluid viscosity. Size equal to number
/// of elements in \code pg.data \endcode.
std::vector<double>
viscosity(const int region,
const VaporizedOil& rv,
const GasPressure& pg) const;
/// Retrieve constant mass density of gas at surface conditions.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \return Mass density of gas at surface in particular model
/// region.
double surfaceMassDensity(const int region) const;
/// Retrieve 2D graph representation of Gas PVT property function in
/// partcular PVT region.
///
/// \param[in] curve PVT property curve descriptor
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \return 2D graph for PVT property curve identified by
/// requests represented by \p func and \p region.
///
/// Example: Retrieve gas formation volume factor curve in PVT
/// region 0 (zero based, i.e., cells for which PVTNUM==1).
///
/// \code
/// const auto graph =
/// pvtGas.getPvtCurve(ECLPVT::RawCurve::FVF, 0);
/// \endcode
FlowDiagnostics::Graph
getPvtCurve(const RawCurve curve, const int region) const;
private:
/// Implementation class.
class Impl;
/// Pointer to implementation.
std::unique_ptr<Impl> pImpl_;
};
/// Basic Gas PVT Relation Interpolant Factory Functions.
struct CreateGasPVTInterpolant
{
/// Create Gas PVT interpolant directly from an ECL result set
/// (i.e., from the tabulated functions in the 'TAB' vector.
///
/// \param[in] init ECL result set INIT file representation.
///
/// \return Gas PVT interpolant. Nullpointer if gas is not an
/// active phase in the result set.
static std::unique_ptr<Gas>
fromECLOutput(const ECLInitFileData& init);
};
}} // Opm::ECLPVT
#endif // OPM_ECLPVTGAS_HEADER_INCLUDED

View File

@ -0,0 +1,603 @@
/*
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/ECLPvtOil.hpp>
#include <opm/utility/ECLPropTable.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <cassert>
#include <cmath>
#include <exception>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <ert/ecl/ecl_kw_magic.h>
namespace {
::Opm::ECLPVT::ConvertUnits
deadOilUnitConverter(const ::Opm::ECLUnits::UnitSystem& usys)
{
using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI;
// [ Po, 1/B, 1/(B*mu), d(1/B)/dPo, d(1/(B*mu))/dPo ]
return ::Opm::ECLPVT::ConvertUnits {
ToSI::pressure(usys),
{
ToSI::recipFvf(usys),
ToSI::recipFvfVisc(usys),
ToSI::recipFvfDerivPress(usys),
ToSI::recipFvfViscDerivPress(usys)
}
};
}
::Opm::ECLPVT::ConvertUnits deadOilUnitConverter(const int usys)
{
const auto u = ::Opm::ECLUnits::createUnitSystem(usys);
return deadOilUnitConverter(*u);
}
std::pair< ::Opm::ECLPVT::ConvertUnits::Converter,
::Opm::ECLPVT::ConvertUnits>
liveOilUnitConverter(const int usys)
{
using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI;
const auto u = ::Opm::ECLUnits::createUnitSystem(usys);
// Key = Rs
// Table = [ Po, 1/B, 1/(B*mu), d(1/B)/dPo, d(1/(B*mu))/dPo ]
// = dead oil table format.
return std::make_pair(ToSI::disGas(*u),
deadOilUnitConverter(*u));
}
}
// ---------------------------------------------------------------------
// Enable runtime selection of dead or live oil functions.
class PVxOBase
{
public:
virtual std::vector<double>
formationVolumeFactor(const std::vector<double>& rs,
const std::vector<double>& po) const = 0;
virtual std::vector<double>
viscosity(const std::vector<double>& rs,
const std::vector<double>& po) const = 0;
virtual Opm::FlowDiagnostics::Graph
getPvtCurve(const Opm::ECLPVT::RawCurve curve) const = 0;
virtual std::unique_ptr<PVxOBase> clone() const = 0;
};
// =====================================================================
class DeadOil : public PVxOBase
{
public:
using ElemIt = ::Opm::ECLPVT::PVDx::ElemIt;
using ConvertUnits = ::Opm::ECLPVT::ConvertUnits;
DeadOil(ElemIt xBegin,
ElemIt xEnd,
const ConvertUnits& convert,
std::vector<ElemIt>& colIt)
: interpolant_(xBegin, xEnd, convert, colIt)
{}
virtual std::vector<double>
formationVolumeFactor(const std::vector<double>& /* rs */,
const std::vector<double>& po) const override
{
return this->interpolant_.formationVolumeFactor(po);
}
virtual std::vector<double>
viscosity(const std::vector<double>& /* rs */,
const std::vector<double>& po) const override
{
return this->interpolant_.viscosity(po);
}
virtual Opm::FlowDiagnostics::Graph
getPvtCurve(const Opm::ECLPVT::RawCurve curve) const override
{
return this->interpolant_.getPvtCurve(curve);
}
virtual std::unique_ptr<PVxOBase> clone() const override
{
return std::unique_ptr<PVxOBase>(new DeadOil(*this));
}
private:
::Opm::ECLPVT::PVDx interpolant_;
};
// =====================================================================
class LiveOil : public PVxOBase
{
public:
using Extrap = ::Opm::Interp1D::PiecewisePolynomial::
ExtrapolationPolicy::Linearly;
using SubtableInterpolant = ::Opm::Interp1D::PiecewisePolynomial::
Linear<Extrap, /* IsAscendingRange = */ true>;
LiveOil(std::vector<double> key,
std::vector<SubtableInterpolant> propInterp)
: interp_(std::move(key), std::move(propInterp))
{}
virtual std::vector<double>
formationVolumeFactor(const std::vector<double>& rs,
const std::vector<double>& po) const override
{
// PKey Inner C0 C1 C2 C3
// Rs Po 1/B 1/(B*mu) d(1/B)/dPo d(1/(B*mu))/dPo
// : : : : :
const auto key = TableInterpolant::PrimaryKey { rs };
const auto x = TableInterpolant::InnerVariate { po };
return this->interp_.formationVolumeFactor(key, x);
}
virtual std::vector<double>
viscosity(const std::vector<double>& rs,
const std::vector<double>& po) const override
{
// PKey Inner C0 C1 C2 C3
// Rs Po 1/B 1/(B*mu) d(1/B)/dPo d(1/(B*mu))/dPo
// : : : : :
const auto key = TableInterpolant::PrimaryKey { rs };
const auto x = TableInterpolant::InnerVariate { po };
return this->interp_.viscosity(key, x);
}
virtual Opm::FlowDiagnostics::Graph
getPvtCurve(const Opm::ECLPVT::RawCurve /* curve */) const override
{
throw std::runtime_error {
"Property Evaluator for Live Oil Does not "
"Support Retrieving Raw Curves (Blame BSKA)"
};
}
virtual std::unique_ptr<PVxOBase> clone() const override
{
return std::unique_ptr<PVxOBase>(new LiveOil(*this));
}
private:
using TableInterpolant = ::Opm::ECLPVT::PVTx<SubtableInterpolant>;
TableInterpolant interp_;
};
// #####################################################################
namespace {
std::vector<std::unique_ptr<PVxOBase>>
createDeadOil(const ::Opm::ECLPropTableRawData& raw,
const int usys)
{
using PVTInterp = std::unique_ptr<PVxOBase>;
using ElmIt = ::Opm::ECLPropTableRawData::ElementIterator;
assert ((raw.numPrimary == 1) &&
"Can't Create Dead Oil Function From Live Oil Table");
const auto cvrt = deadOilUnitConverter(usys);
return ::Opm::MakeInterpolants<PVTInterp>::fromRawData(raw,
[&cvrt](ElmIt xBegin, ElmIt xEnd, std::vector<ElmIt>& colIt)
{
return PVTInterp{ new DeadOil(xBegin, xEnd, cvrt, colIt) };
});
}
std::vector<double>
extractPrimaryKey(const ::Opm::ECLPropTableRawData& raw,
const ::Opm::ECLPropTableRawData::SizeType t,
const ::Opm::ECLPVT::ConvertUnits::Converter& cvrtKey)
{
auto key = std::vector<double>{};
key.reserve(raw.numPrimary);
for (auto begin = std::begin(raw.primaryKey) + (t + 0)*raw.numPrimary,
end = begin + raw.numPrimary;
begin != end; ++begin)
{
if (std::abs(*begin) < 1.0e20) {
key.push_back(cvrtKey(*begin));
}
}
return key;
}
std::vector<std::unique_ptr<PVxOBase>>
createLiveOil(const ::Opm::ECLPropTableRawData& raw,
const int usys)
{
auto ret = std::vector<std::unique_ptr<PVxOBase>>{};
ret.reserve(raw.numTables);
using Extrap = LiveOil::Extrap;
using StI = LiveOil::SubtableInterpolant;
using ElemIt = ::Opm::ECLPropTableRawData::ElementIterator;
const auto cvrt = liveOilUnitConverter(usys);
auto sti = ::Opm::MakeInterpolants<StI>::fromRawData(raw,
[&cvrt](ElemIt xBegin,
ElemIt xEnd,
std::vector<ElemIt>& colIt) -> StI
{
try {
return StI(Extrap{}, xBegin, xEnd, colIt,
cvrt.second.indep,
cvrt.second.column);
}
catch (const std::invalid_argument&) {
// No valid nodes. Return invalid.
return StI(Extrap{});
}
});
for (auto t = 0*raw.numTables;
t < raw.numTables; ++t)
{
auto key = extractPrimaryKey(raw, t, cvrt.first);
const auto begin = (t + 0)*raw.numPrimary;
const auto end = begin + key.size();
ret.emplace_back(new LiveOil(std::move(key),
{
std::make_move_iterator(std::begin(sti) + begin),
std::make_move_iterator(std::begin(sti) + end)
}));
}
return ret;
}
std::vector<std::unique_ptr<PVxOBase>>
createPVTFunction(const ::Opm::ECLPropTableRawData& raw,
const int usys)
{
if (raw.numPrimary == 0) {
// Malformed Gas PVT table.
throw std::invalid_argument {
"Oil PVT Table Without Primary Lookup Key"
};
}
if (raw.numCols != 5) {
throw std::invalid_argument {
"PVT Table for Oil Must Have Five Columns"
};
}
if (raw.primaryKey.size() != (raw.numPrimary * raw.numTables)) {
throw std::invalid_argument {
"Size Mismatch in RS Nodes of PVT Table for Oil"
};
}
if (raw.data.size() !=
(raw.numPrimary * raw.numRows * raw.numCols * raw.numTables))
{
throw std::invalid_argument {
"Size Mismatch in Condensed Table Data "
"of PVT Table for Oil"
};
}
if (raw.numPrimary == 1) {
return createDeadOil(raw, usys);
}
return createLiveOil(raw, usys);
}
std::vector<std::unique_ptr<PVxOBase>>
clone(const std::vector<std::unique_ptr<PVxOBase>>& src)
{
auto dest = std::vector<std::unique_ptr<PVxOBase>>{};
dest.reserve(src.size());
for (const auto& p : src) {
dest.push_back(p->clone());
}
return dest;
}
}
// #####################################################################
// #####################################################################
// =====================================================================
// Class ECLPVT::Oil::Impl
// ---------------------------------------------------------------------
class Opm::ECLPVT::Oil::Impl
{
private:
using EvalPtr = std::unique_ptr<PVxOBase>;
public:
Impl(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS);
Impl(const Impl& rhs);
Impl(Impl&& rhs);
Impl& operator=(const Impl& rhs);
Impl& operator=(Impl&& rhs);
using RegIdx = std::vector<EvalPtr>::size_type;
std::vector<double>
formationVolumeFactor(const RegIdx region,
const std::vector<double>& rs,
const std::vector<double>& po) const;
std::vector<double>
viscosity(const RegIdx region,
const std::vector<double>& rs,
const std::vector<double>& po) const;
double surfaceMassDensity(const RegIdx region) const
{
this->validateRegIdx(region);
return this->rhoS_[region];
}
FlowDiagnostics::Graph
getPvtCurve(const RegIdx region,
const RawCurve curve) const;
private:
std::vector<EvalPtr> eval_;
std::vector<double> rhoS_;
void validateRegIdx(const RegIdx region) const;
};
Opm::ECLPVT::Oil::Impl::
Impl(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS)
: eval_(createPVTFunction(raw, usys))
, rhoS_(std::move(rhoS))
{}
Opm::ECLPVT::Oil::Impl::Impl(const Impl& rhs)
: eval_(clone(rhs.eval_))
, rhoS_(rhs.rhoS_)
{}
Opm::ECLPVT::Oil::Impl::Impl(Impl&& rhs)
: eval_(std::move(rhs.eval_))
, rhoS_(std::move(rhs.rhoS_))
{}
Opm::ECLPVT::Oil::Impl&
Opm::ECLPVT::Oil::Impl::operator=(const Impl& rhs)
{
this->eval_ = clone(rhs.eval_);
this->rhoS_ = rhs.rhoS_;
return *this;
}
Opm::ECLPVT::Oil::Impl&
Opm::ECLPVT::Oil::Impl::operator=(Impl&& rhs)
{
this->eval_ = std::move(rhs.eval_);
this->rhoS_ = std::move(rhs.rhoS_);
return *this;
}
std::vector<double>
Opm::ECLPVT::Oil::Impl::
formationVolumeFactor(const RegIdx region,
const std::vector<double>& rs,
const std::vector<double>& po) const
{
this->validateRegIdx(region);
return this->eval_[region]->formationVolumeFactor(rs, po);
}
std::vector<double>
Opm::ECLPVT::Oil::Impl::
viscosity(const RegIdx region,
const std::vector<double>& rs,
const std::vector<double>& po) const
{
this->validateRegIdx(region);
return this->eval_[region]->viscosity(rs, po);
}
Opm::FlowDiagnostics::Graph
Opm::ECLPVT::Oil::Impl::
getPvtCurve(const RegIdx region,
const RawCurve curve) const
{
this->validateRegIdx(region);
return this->eval_[region]->getPvtCurve(curve);
}
void
Opm::ECLPVT::Oil::Impl::validateRegIdx(const RegIdx region) const
{
if (region >= this->eval_.size()) {
throw std::invalid_argument {
"Region Index " +
std::to_string(region) +
" Outside Valid Range (0 .. " +
std::to_string(this->eval_.size() - 1) + ')'
};
}
}
// ======================================================================
// Class ECLPVT::Oil
// ----------------------------------------------------------------------
Opm::ECLPVT::Oil::Oil(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS)
: pImpl_(new Impl(raw, usys, std::move(rhoS)))
{}
Opm::ECLPVT::Oil::~Oil()
{}
Opm::ECLPVT::Oil::Oil(const Oil& rhs)
: pImpl_(new Impl(*rhs.pImpl_))
{}
Opm::ECLPVT::Oil::Oil(Oil&& rhs)
: pImpl_(std::move(rhs.pImpl_))
{}
Opm::ECLPVT::Oil&
Opm::ECLPVT::Oil::operator=(const Oil& rhs)
{
this->pImpl_.reset(new Impl(*rhs.pImpl_));
return *this;
}
Opm::ECLPVT::Oil&
Opm::ECLPVT::Oil::operator=(Oil&& rhs)
{
this->pImpl_ = std::move(rhs.pImpl_);
return *this;
}
std::vector<double>
Opm::ECLPVT::Oil::
formationVolumeFactor(const int region,
const DissolvedGas& rs,
const OilPressure& po) const
{
return this->pImpl_->formationVolumeFactor(region, rs.data, po.data);
}
std::vector<double>
Opm::ECLPVT::Oil::
viscosity(const int region,
const DissolvedGas& rs,
const OilPressure& po) const
{
return this->pImpl_->viscosity(region, rs.data, po.data);
}
double Opm::ECLPVT::Oil::surfaceMassDensity(const int region) const
{
return this->pImpl_->surfaceMassDensity(region);
}
Opm::FlowDiagnostics::Graph
Opm::ECLPVT::Oil::
getPvtCurve(const RawCurve curve, const int region) const
{
return this->pImpl_->getPvtCurve(region, curve);
}
// =====================================================================
std::unique_ptr<Opm::ECLPVT::Oil>
Opm::ECLPVT::CreateOilPVTInterpolant::
fromECLOutput(const ECLInitFileData& init)
{
using OPtr = std::unique_ptr<Opm::ECLPVT::Oil>;
const auto& ih = init.keywordData<int>(INTEHEAD_KW);
const auto iphs = static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]);
if ((iphs & (1u << 0)) == 0) {
// Oil is not an active phase (unexpected).
// Return sentinel (null) pointer.
return OPtr{};
}
auto raw = ::Opm::ECLPropTableRawData{};
const auto& tabdims = init.keywordData<int>("TABDIMS");
const auto& tab = init.keywordData<double>("TAB");
raw.numPrimary = tabdims[ TABDIMS_NRPVTO_ITEM ]; // #Rs nodes/full table
raw.numRows = tabdims[ TABDIMS_NPPVTO_ITEM ]; // #Po nodes/sub-table
raw.numCols = 5; // [ Po, 1/B, 1/(B*mu), d(1/B)/dPo, d(1/(B*mu))/dPo ]
raw.numTables = tabdims[ TABDIMS_NTPVTO_ITEM ]; // # PVTO tables
// Extract Primary Key (Rs)
{
const auto nTabElem = raw.numPrimary * raw.numTables;
// Subtract one to account for 1-based indices.
const auto start = tabdims[ TABDIMS_JBPVTO_OFFSET_ITEM ] - 1;
raw.primaryKey.assign(&tab[start], &tab[start] + nTabElem);
}
// Extract Full Table
{
const auto nTabElem =
raw.numPrimary * raw.numRows * raw.numCols * raw.numTables;
// Subtract one to account for 1-based indices.
const auto start = tabdims[ TABDIMS_IBPVTO_OFFSET_ITEM ] - 1;
raw.data.assign(&tab[start], &tab[start] + nTabElem);
}
auto rhoS = surfaceMassDensity(init, ECLPhaseIndex::Liquid);
return OPtr{
new Oil(raw, ih[ INTEHEAD_UNIT_INDEX ], std::move(rhoS))
};
}

View File

@ -0,0 +1,197 @@
/*
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_ECLPVTOIL_HEADER_INCLUDED
#define OPM_ECLPVTOIL_HEADER_INCLUDED
#include <opm/utility/ECLPvtCommon.hpp>
#include <memory>
#include <vector>
// Forward declarations
namespace Opm {
struct ECLPropTableRawData;
class ECLInitFileData;
} // Opm
namespace Opm { namespace ECLPVT {
/// Interpolant for Basic Oil PVT Relations.
class Oil
{
public:
/// Constructor.
///
/// \param[in] raw Raw tabulated data. Must correspond to the PVTO
/// vector of an ECL INIT file..
///
/// \param[in] usys Unit system convention of the result set from
/// which \p raw was extracted. Must correspond to item 3 of the
/// INTEHEAD keyword in the INIT file.
///
/// \param[in] rhoS Mass density of oil at surface conditions.
/// Typically computed by \code ECLPVT::surfaceMassDensity()
/// \endcode.
Oil(const ECLPropTableRawData& raw,
const int usys,
std::vector<double> rhoS);
/// Destructor.
~Oil();
/// Copy constructor.
///
/// \param[in] rhs Existing interpolant for Oil PVT relations.
Oil(const Oil& rhs);
/// Move constructor.
///
/// Subsumes the implementation of an existing Oil PVT relation
/// interpolant.
///
/// \param[in] rhs Existing Oil PVT relation interpolant. Does not
/// have a valid implementation when the constructor completes.
Oil(Oil&& rhs);
/// Assignment operator
///
/// \param[in] rhs Existing Oil PVT relation interpolant.
///
/// \return \code *this \endcode.
Oil& operator=(const Oil& rhs);
/// Move assignment operator.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing Oil PVT relation interpolant. Does not
/// have a valid implementation when the constructor completes.
///
/// \return \code *this \endcode.
Oil& operator=(Oil&& rhs);
/// Representation of the Dissolved Gas-Oil Ratio (Rs).
///
/// Mostly as a convenience to disambiguate the client interface.
struct DissolvedGas {
/// Dissolved gas-oil ratio data for all sampling points.
std::vector<double> data;
};
/// Representation of Oil Phase Pressure (Po).
///
/// Mostly as a convenience to disambiguate the client interface.
struct OilPressure {
/// Oil phase pressure for all sampling points.
std::vector<double> data;
};
/// Compute the oil phase formation volume factor in a single region.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \param[in] rs Dissolved gas-oil ratio. Unused in the case of a
/// dead oil model. Size must match number of elements in \code
/// po.data \endcode in the case of live oil model. Strict SI
/// units of measurement.
///
/// \param[in] po Oil phase pressure. Strict SI units of measurement.
///
/// \return Oil phase formation volume factor. Size equal to number
/// of elements in \code po.data \endcode.
std::vector<double>
formationVolumeFactor(const int region,
const DissolvedGas& rs,
const OilPressure& po) const;
/// Compute the oil phase fluid viscosity in a single region.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \param[in] rs Dissolved gas-oil ratio. Unused in the case of a
/// dead oil model. Size must match number of elements in \code
/// po.data \endcode in the case of live oil model. Strict SI
/// units of measurement.
///
/// \param[in] po Oil phase pressure. Strict SI units of measurement.
///
/// \return Oil phase fluid viscosity. Size equal to number of
/// elements in \code po.data \endcode.
std::vector<double>
viscosity(const int region,
const DissolvedGas& rs,
const OilPressure& po) const;
/// Retrieve constant mass density of oil at surface conditions.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \return Mass density of oil at surface in particular model
/// region.
double surfaceMassDensity(const int region) const;
/// Retrieve 2D graph representation of Oil PVT property function in
/// partcular PVT region.
///
/// \param[in] curve PVT property curve descriptor
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \return 2D graph for PVT property curve identified by
/// requests represented by \p func and \p region.
///
/// Example: Retrieve oil viscosity curve in PVT region 3 (zero
/// based, i.e., those cells for which PVTNUM==4).
///
/// \code
/// const auto graph =
/// pvtOil.getPvtCurve(ECLPVT::RawCurve::Viscosity, 3);
/// \endcode
FlowDiagnostics::Graph
getPvtCurve(const RawCurve curve, const int region) const;
private:
/// Implementation class.
class Impl;
/// Pointer to implementation.
std::unique_ptr<Impl> pImpl_;
};
/// Basic Oil PVT Relation Interpolant Factory Functions.
struct CreateOilPVTInterpolant
{
/// Create Oil PVT interpolant directly from an ECL result set
/// (i.e., from the tabulated functions in the 'TAB' vector.
///
/// \param[in] init ECL result set INIT file representation.
///
/// \return Oil PVT interpolant. Nullpointer if oil is not an
/// active phase in the result set (unexpected).
static std::unique_ptr<Oil>
fromECLOutput(const ECLInitFileData& init);
};
}} // Opm::ECLPVT
#endif // OPM_ECLPVTOIL_HEADER_INCLUDED

View File

@ -0,0 +1,364 @@
/*
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/ECLPvtWater.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <opm/utility/ECLPropTable.hpp>
#include <opm/utility/ECLPvtCommon.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <cassert>
#include <cmath>
#include <exception>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <ert/ecl/ecl_kw_magic.h>
namespace {
::Opm::ECLPVT::ConvertUnits waterUnitConverter(const int usys)
{
using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI;
const auto u = ::Opm::ECLUnits::createUnitSystem(usys);
// [ Pref, 1/Bw, Cw, 1/(Bw*mu_w), Cw - Cv ]
return ::Opm::ECLPVT::ConvertUnits {
ToSI::pressure(*u),
{
ToSI::recipFvf(*u),
ToSI::compressibility(*u),
ToSI::recipFvfVisc(*u),
ToSI::compressibility(*u)
}
};
}
} // Anonymous
class PVTCurves
{
public:
using ElemIt = std::vector<double>::const_iterator;
using ConvertUnits = ::Opm::ECLPVT::ConvertUnits;
PVTCurves(ElemIt xBegin,
ElemIt xEnd,
const ConvertUnits& convert,
std::vector<ElemIt>& colIt);
std::vector<double>
formationVolumeFactor(const std::vector<double>& pw) const
{
return this->evaluate(pw, [this](const double p) -> double
{
// 1 / (1 / B)
return 1.0 / this->recipFvf(p);
});
}
std::vector<double>
viscosity(const std::vector<double>& pw) const
{
return this->evaluate(pw, [this](const double p) -> double
{
// (1 / B) / (1 / (B * mu))
return this->recipFvf(p) / this->recipFvfVisc(p);
});
}
double& surfaceMassDensity()
{
return this->rhoS_;
}
double surfaceMassDensity() const
{
return this->rhoS_;
}
private:
double pw_ref_ { 1.0 };
double recipFvf_ { 1.0 }; // 1 / B
double recipFvfVisc_ { 1.0 }; // 1 / (B*mu)
double Cw_ { 1.0 };
double diffCwCv_ { 0.0 }; // Cw - Cv
double rhoS_ { 0.0 };
double recipFvf(const double pw) const
{
const auto x = this->Cw_ * (pw - this->pw_ref_);
return this->recipFvf_ * this->exp(x);
}
double recipFvfVisc(const double pw) const
{
const auto y = this->diffCwCv_ * (pw - this->pw_ref_);
return this->recipFvfVisc_ * this->exp(y);
}
double exp(const double x) const
{
return 1.0 + x*(1.0 + x/2.0);
}
template <class CalcQuant>
std::vector<double>
evaluate(const std::vector<double>& pw,
CalcQuant&& calculate) const
{
auto q = std::vector<double>{};
q.reserve(pw.size());
for (const auto& pwi : pw) {
q.push_back(calculate(pwi));
}
return q;
}
};
PVTCurves::PVTCurves(ElemIt xBegin,
ElemIt xEnd,
const ConvertUnits& convert,
std::vector<ElemIt>& colIt)
{
assert ((std::distance(xBegin, xEnd) == 1) &&
"Logic Error in Defining PVTW Input Ranges");
#ifdef NDEBUG
// Suppress "unusued variable" in release mode.
static_cast<void>(xEnd);
#endif // NDEBUG
// Recall: Table is
//
// [ Pw, 1/Bw, Cw, 1/(Bw*mu_w), Cw - Cv ]
//
// xBegin is Pw, colIt is remaining four columns.
this->recipFvf_ = convert.column[0](*colIt[0]); // 1/Bw
this->Cw_ = convert.column[1](*colIt[1]); // Cw
this->recipFvfVisc_ = convert.column[2](*colIt[2]); // 1/(Bw*mu_w)
this->diffCwCv_ = convert.column[3](*colIt[3]); // Cw - Cv
// Honour requirement that constructor advances column iterators.
for (auto& it : colIt) { ++it; }
if (! (std::abs(*xBegin) < 1.0e20)) {
throw std::invalid_argument {
"Invalid Input PVTW Table"
};
}
this->pw_ref_ = convert.indep(*xBegin);
}
// #####################################################################
// #####################################################################
// =====================================================================
// Class ECLPVT::Water::Impl
// ---------------------------------------------------------------------
class Opm::ECLPVT::Water::Impl
{
public:
Impl(const ECLPropTableRawData& raw,
const int usys,
const std::vector<double>& rhoS);
using RegIdx = std::vector<PVTCurves>::size_type;
std::vector<double>
formationVolumeFactor(const RegIdx region,
const std::vector<double>& pw) const
{
this->validateRegIdx(region);
return this->eval_[region].formationVolumeFactor(pw);
}
std::vector<double>
viscosity(const RegIdx region,
const std::vector<double>& pw) const
{
this->validateRegIdx(region);
return this->eval_[region].viscosity(pw);
}
double surfaceMassDensity(const RegIdx region) const
{
this->validateRegIdx(region);
return this->eval_[region].surfaceMassDensity();
}
private:
std::vector<PVTCurves> eval_;
void validateRegIdx(const RegIdx region) const;
};
Opm::ECLPVT::Water::Impl::Impl(const ECLPropTableRawData& raw,
const int usys,
const std::vector<double>& rhoS)
{
using ElemIt = PVTCurves::ElemIt;
const auto cvrt = waterUnitConverter(usys);
this->eval_ = MakeInterpolants<PVTCurves>::fromRawData(raw,
[&cvrt](ElemIt xBegin,
ElemIt xEnd,
std::vector<ElemIt>& colIt) -> PVTCurves
{
return PVTCurves(xBegin, xEnd, cvrt, colIt);
});
assert (rhoS.size() == this->eval_.size());
for (auto n = this->eval_.size(), i = 0*n; i < n; ++i) {
this->eval_[i].surfaceMassDensity() = rhoS[i];
}
}
void
Opm::ECLPVT::Water::Impl::validateRegIdx(const RegIdx region) const
{
if (region >= this->eval_.size()) {
throw std::invalid_argument {
"Region Index " +
std::to_string(region) +
" Outside Valid Range (0 .. " +
std::to_string(this->eval_.size() - 1) + ')'
};
}
}
// =====================================================================
// Class ECLPVT::Water
// ---------------------------------------------------------------------
Opm::ECLPVT::Water::Water(const ECLPropTableRawData& raw,
const int usys,
const std::vector<double>& rhoS)
: pImpl_(new Impl(raw, usys, rhoS))
{}
Opm::ECLPVT::Water::~Water()
{}
Opm::ECLPVT::Water::Water(const Water& rhs)
: pImpl_(new Impl(*rhs.pImpl_))
{}
Opm::ECLPVT::Water::Water(Water&& rhs)
: pImpl_(std::move(rhs.pImpl_))
{}
Opm::ECLPVT::Water&
Opm::ECLPVT::Water::operator=(const Water& rhs)
{
this->pImpl_.reset(new Impl(*rhs.pImpl_));
return *this;
}
Opm::ECLPVT::Water&
Opm::ECLPVT::Water::operator=(Water&& rhs)
{
this->pImpl_ = std::move(rhs.pImpl_);
return *this;
}
std::vector<double>
Opm::ECLPVT::Water::formationVolumeFactor(const int region,
const WaterPressure& pw) const
{
return this->pImpl_->formationVolumeFactor(region, pw.data);
}
std::vector<double>
Opm::ECLPVT::Water::viscosity(const int region,
const WaterPressure& pw) const
{
return this->pImpl_->viscosity(region, pw.data);
}
double
Opm::ECLPVT::Water::surfaceMassDensity(const int region) const
{
return this->pImpl_->surfaceMassDensity(region);
}
// =====================================================================
std::unique_ptr<Opm::ECLPVT::Water>
Opm::ECLPVT::CreateWaterPVTInterpolant::
fromECLOutput(const ECLInitFileData& init)
{
using WPtr = ::std::unique_ptr<Opm::ECLPVT::Water>;
const auto& ih = init.keywordData<int>(INTEHEAD_KW);
const auto iphs = static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]);
if ((iphs & (1u << 1)) == 0) {
// Water is not an active phase.
// Return sentinel (null) pointer.
return WPtr{};
}
auto raw = ::Opm::ECLPropTableRawData{};
const auto& tabdims = init.keywordData<int>("TABDIMS");
const auto& tab = init.keywordData<double>("TAB");
raw.numPrimary = 1; // Single record per region
raw.numRows = 1; // Single record per region
raw.numCols = 5; // [ Pw, 1/B, Cw, 1/(B*mu), Cw - Cv ]
raw.numTables = tabdims[ TABDIMS_NTPVTW_ITEM ]; // # PVTW tables
// Extract Full Table
{
const auto nTabElem =
raw.numPrimary * raw.numRows * raw.numCols * raw.numTables;
// Subtract one to account for 1-based indices.
const auto start = tabdims[ TABDIMS_IBPVTW_OFFSET_ITEM ] - 1;
raw.data.assign(&tab[start], &tab[start] + nTabElem);
}
const auto rhoS = surfaceMassDensity(init, ECLPhaseIndex::Aqua);
return WPtr{
new Water(raw, ih[ INTEHEAD_UNIT_INDEX ], rhoS)
};
}

View File

@ -0,0 +1,153 @@
/*
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_ECLPVTWATER_HEADER_INCLUDED
#define OPM_ECLPVTWATER_HEADER_INCLUDED
#include <memory>
#include <vector>
namespace Opm {
struct ECLPropTableRawData;
class ECLInitFileData;
} // Opm
namespace Opm { namespace ECLPVT {
/// Interpolant for Basic Water PVT Relations.
class Water
{
public:
/// Constructor.
///
/// \param[in] raw Raw tabulated data. Must correspond to the PVTW
/// vector of an ECL INIT file..
///
/// \param[in] usys Unit system convention of the result set from
/// which \p raw was extracted. Must correspond to item 3 of the
/// INTEHEAD keyword in the INIT file.
///
/// \param[in] rhoS Mass density of water at surface conditions.
/// Typically computed by \code ECLPVT::surfaceMassDensity()
/// \endcode.
Water(const ECLPropTableRawData& raw,
const int usys,
const std::vector<double>& rhoS);
/// Destructor.
~Water();
/// Copy constructor.
///
/// \param[in] rhs Existing interpolant for Water PVT relations.
Water(const Water& rhs);
/// Move constructor.
///
/// Subsumes the implementation of an existing Water PVT relation
/// interpolant.
///
/// \param[in] rhs Existing Water PVT relation interpolant. Does not
/// have a valid implementation when the constructor completes.
Water(Water&& rhs);
/// Assignment operator
///
/// \param[in] rhs Existing Oil Water relation interpolant.
///
/// \return \code *this \endcode.
Water& operator=(const Water& rhs);
/// Move assignment operator.
///
/// Subsumes the implementation of an existing object.
///
/// \param[in] rhs Existing Water PVT relation interpolant. Does not
/// have a valid implementation when the constructor completes.
///
/// \return \code *this \endcode.
Water& operator=(Water&& rhs);
/// Representation of Water Phase Pressure (Pw).
///
/// Mostly as a convenience to disambiguate the client interface.
struct WaterPressure {
/// Water phase pressure for all sampling points.
std::vector<double> data;
};
/// Compute the oil phase formation volume factor in a single region.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \param[in] pw Water phase pressure. Strict SI units of measurement.
///
/// \return Oil phase formation volume factor. Size equal to number
/// of elements in \code pw.data \endcode.
std::vector<double>
formationVolumeFactor(const int region,
const WaterPressure& pw) const;
/// Compute the water phase fluid viscosity in a single region.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \param[in] pw Water phase pressure. Strict SI units of measurement.
///
/// \return Oil phase fluid viscosity. Size equal to number of
/// elements in \code pw.data \endcode.
std::vector<double>
viscosity(const int region,
const WaterPressure& pw) const;
/// Retrieve constant mass density of water at surface conditions.
///
/// \param[in] region Region ID. Non-negative integer typically
/// derived from the PVTNUM mapping vector.
///
/// \return Mass density of water at surface in particular model
/// region.
double surfaceMassDensity(const int region) const;
private:
/// Implementation class.
class Impl;
/// Pointer to implementation.
std::unique_ptr<Impl> pImpl_;
};
/// Basic Oil PVT Relation Interpolant Factory Functions.
struct CreateWaterPVTInterpolant
{
/// Create Oil PVT interpolant directly from an ECL result set
/// (i.e., from the tabulated functions in the 'TAB' vector.
///
/// \param[in] init ECL result set INIT file representation.
///
/// \return Oil PVT interpolant. Nullpointer if water is not an
/// active phase in the result set.
static std::unique_ptr<Water>
fromECLOutput(const ECLInitFileData& init);
};
}} // Opm::ECLPVT
#endif // OPM_ECLPVTWATER_HEADER_INCLUDED

View File

@ -0,0 +1,143 @@
/*
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/ECLRegionMapping.hpp>
#include <algorithm>
#include <exception>
#include <iterator>
#include <numeric>
#include <stdexcept>
#include <utility>
namespace {
std::vector<int>
makeRegionSubset(const std::vector<int>::size_type n,
std::vector<int> regSubset)
{
auto ret = std::move(regSubset);
if (ret.empty()) {
ret.resize(n);
std::iota(std::begin(ret), std::end(ret), 0);
}
return ret;
}
} // Anonymous
Opm::ECLRegionMapping::
ECLRegionMapping(const std::vector<int>& region,
const std::vector<int>& regSubset)
: regSubset_(makeRegionSubset(region.size(), regSubset))
{
{
auto i = 0;
for (const auto& ix : this->regSubset_) {
this->regionSubsetIndex_
.addConnection(this->activeID(region[ix]), i++);
}
}
if (this->next_ == this->start_) {
// No active region IDs. Typically empty 'region' or 'regSubset'.
// Invalid nonetheless so we can't continue here.
throw std::invalid_argument {
"Requested Region/Index Vector Pair "
"Does not Form Valid Subset"
};
}
// Recall: next_ represents the dense, linear ID that would be assigned
// to the "next" unseen region ID in a "start_"-based index sequence.
//
// Therefore next_ - start_ is the number of linear IDs assigned during
// this construction process which is also the number of rows in the
// sparse mapping matrix.
this->regionSubsetIndex_.compress(this->next_ - this->start_);
}
const std::vector<int>&
Opm::ECLRegionMapping::regionSubset() const
{
return this->regSubset_;
}
std::vector<int>
Opm::ECLRegionMapping::activeRegions() const
{
auto areg = std::vector<int>{};
areg.reserve(activeID_.size());
for (const auto& reg : this->activeID_) {
areg.push_back(reg.first);
}
std::sort(std::begin(areg), std::end(areg));
return areg;
}
Opm::ECLRegionMapping::IndexView
Opm::ECLRegionMapping::getRegionIndices(const int region) const
{
const auto areg = this->activeID(region);
if (areg < 0) {
throw std::logic_error {
"Region ID not in configured subset"
};
}
const auto& start = this->regionSubsetIndex_.startPointers();
const auto& ix = this->regionSubsetIndex_.neighbourhood();
auto begin = std::begin(ix) + start[areg + 0];
auto end = std::begin(ix) + start[areg + 1];
return { begin, end };
}
int Opm::ECLRegionMapping::activeID(const int regID)
{
auto& areg = this->activeID_[regID];
if (areg == 0) {
// Region ID 'regID' not previously seen. Assign new active ID.
areg = this->next_++;
}
return areg - this->start_;
}
int Opm::ECLRegionMapping::activeID(const int regID) const
{
auto i = this->activeID_.find(regID);
if (i == std::end(this->activeID_)) {
// Region ID 'regID' not in configured subset of ID set defined on
// active cells.
return -1;
}
return i->second - this->start_;
}

View File

@ -0,0 +1,130 @@
/*
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_ECLREGIONMAPPING_HEADER_INCLUDED
#define OPM_ECLREGIONMAPPING_HEADER_INCLUDED
#include <opm/utility/graph/AssembledConnections.hpp>
#include <opm/utility/graph/AssembledConnectionsIteration.hpp>
#include <unordered_map>
#include <vector>
namespace Opm {
/// Mapping of region IDs to subsets of explicit cell ID collection.
///
/// The typical client code is
/// \code
/// const auto map = ECLRegionMapping(/* ... */);
///
/// /// ...
///
/// const auto& subset = map.regionSubset();
///
/// for (const auto& regID : map.activeRegions()) {
/// for (const auto& ix : map.getRegionIndices(regID)) {
/// use(regID, subset[ix]);
/// }
/// }
/// \endcode
class ECLRegionMapping
{
public:
/// Constructor.
///
/// \param[in] region Container of region IDs. Typically one of the
/// explicit region ID vectors in an ECL result set such as
/// SATNUM or PVTNUM. Assumed to be defined on all active cells
/// of an ECL result set.
///
/// \param[in] regSubset Index subset of region IDs. This object
/// instance partitions the linear indices in
/// \code
/// [ 0 .. regSubset.size()-1 ]
/// \endcode
/// according to the corresponding unique region ID in the set
/// \code
/// { region[ i ] }_{i \in regSubset}
/// \endcode
///
/// If empty or defaulted, \p regSubset is treated as if it were
/// specified as \code [ 0 .. region.size() - 1] \endcode. The
/// common use case for this is working on the entire set of
/// active cells implied by the region ID vector.
///
/// The typical use case of an explicit region subset is when
/// sampling PVT or saturation function curves for graphical
/// representation.
ECLRegionMapping(const std::vector<int>& region,
const std::vector<int>& regSubset
= std::vector<int>());
/// Retrieve index subset.
///
/// Identical to constructor argument \c regSubset if supplied,
/// otherwise the vector \code [ 0 .. region.size()-1 ] \endcode.
const std::vector<int>& regionSubset() const;
/// Retrieve sorted list of unique region IDs in the subset defined
/// by \code { region[i] }_{i \in regionSubset()} \endcode.
std::vector<int> activeRegions() const;
/// Convenience alias to simplify declaring return type of member
/// function \code getRegionIndices() \endcode.
using IndexView = SimpleIteratorRange<
AssembledConnections::Neighbours::const_iterator>;
/// Retrive linear indices into \code regionSubset() \endcode that
/// correspond to particular region ID.
///
/// \param[in] region Numeric region ID. Must be one of the unique
/// region IDs implied by \code activeRegions() \endcode. If it
/// is not one of those IDs, function \code getRegionIndices()
/// \endcode throws an instance of \code std::logic_error
/// \endcode.
///
/// \return Linear index view into \code regionSubset() \endcode.
IndexView getRegionIndices(const int region) const;
private:
/// Offset from which to start assigning linear, dense active IDs.
int start_{1};
/// Next, unassigned linear ID.
int next_{1};
/// Subset of full region ID mapping.
std::vector<int> regSubset_;
/// Sparse mapping of region IDs to dense active IDs.
std::unordered_map<int, int> activeID_;
/// Map active IDs to subsets of the initial index subset.
AssembledConnections regionSubsetIndex_;
/// Translate region ID to dense linear active ID. Mutable version.
int activeID(const int regID);
/// Translate region ID to dense linear active ID. Immutable
/// version.
int activeID(const int regID) const;
};
} // Opm
#endif // OPM_ECLREGIONMAPPING_HEADER_INCLUDED

View File

@ -48,13 +48,10 @@
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl/ecl_nnc_export.h>
#include <ert/ecl/ecl_type.h>
#include <ert/ecl/ecl_util.h>
#include <ert/util/ert_unique_ptr.hpp>
#if defined(HAVE_ERT_ECL_TYPE_H) && HAVE_ERT_ECL_TYPE_H
#include <ert/ecl/ecl_type.h>
#endif // defined(HAVE_ERT_ECL_TYPE_H) && HAVE_ERT_ECL_TYPE_H
/// \file
///
/// Implementation of ECL Result-Set Interface.
@ -63,13 +60,7 @@ namespace {
inline ecl_type_enum
getKeywordElementType(const ecl_kw_type* kw)
{
#if defined(HAVE_ERT_ECL_TYPE_H) && HAVE_ERT_ECL_TYPE_H
return ecl_type_get_type(ecl_kw_get_data_type(kw));
#else // ! (defined(HAVE_ERT_ECL_TYPE_H) && HAVE_ERT_ECL_TYPE_H)
return ecl_kw_get_type(kw);
#endif // defined(HAVE_ERT_ECL_TYPE_H) && HAVE_ERT_ECL_TYPE_H
}
namespace ECLImpl {
@ -259,13 +250,8 @@ namespace {
/// 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);
x[i] = ecl_kw_iget_bool(kw, i);
}
}
};
@ -1380,7 +1366,7 @@ namespace Opm {
this->setActiveBlock(kwloc.sectID);
if (! gridName.empty()) {
// We're cons
// Local grid. Further restrict view to relevant LGR section.
this->activeBlock_ =
ecl_file_view_add_blockview(this->activeBlock_, LGR_KW,
kwloc.gridSectID);

View File

@ -20,6 +20,7 @@
#ifndef OPM_ECLSATURATIONFUNC_HEADER_INCLUDED
#define OPM_ECLSATURATIONFUNC_HEADER_INCLUDED
#include <opm/flowdiagnostics/DerivedQuantities.hpp>
#include <opm/utility/ECLPhaseIndex.hpp>
#include <memory>
@ -43,6 +44,36 @@ namespace Opm {
class ECLSaturationFunc
{
public:
/// Protocol for describing a particular saturation function
/// request.
struct RawCurve
{
/// Which saturation function does this request reference.
enum class Function {
/// Relative permeability functions
RelPerm,
};
/// Which one-dimensional sub-system does this request reference.
enum class SubSystem {
/// Oil-Gas subsystem
OilGas,
/// Oil-Water subsystem
OilWater,
};
/// Particular saturation function of this request.
Function curve;
/// Particular sub-system of this request.
SubSystem subsys;
/// Phase/component for which to form the effective saturation
/// function curve.
ECLPhaseIndex thisPh;
};
/// Constructor
///
/// \param[in] G Connected topology of current model's active cells.
@ -126,6 +157,70 @@ namespace Opm {
const ECLRestartData& rstrt,
const ECLPhaseIndex p) const;
/// Retrieve 2D graph representations of sequence of effective
/// saturation functions in a single cell.
///
/// \param[in] func Sequence of saturation function descriptions.
///
/// \param[in] activeCell Index of active cell from which to derive
/// the effective saturation function. Use member function \code
/// ECLGraph::activeCell() \endcode to translate a global cell
/// (I,J,K) tuple--relative to a model grid--to a linear active
/// cell ID.
///
/// \param[in] useEPS Whether or not to include effects of
/// saturation end-point scaling. No effect if the INIT result
/// set from which the object was constructed 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.
///
/// \return Sequence of 2D graphs for all saturation function
/// requests represented by \p func. In particular, the \c i-th
/// element of the result corresponds to input request \code
/// func[i] \endcode. Abscissas are stored in \code
/// graph[i].first \endcode and ordinates are stored in \code
/// graph[i].second \endcode. If a particular request is
/// semantically invalid, such as when requesting the water
/// relative permeability in the oil-gas system, then the
/// corresponding graph in the result is empty.
///
/// Example: Retrieve relative permeability curves for oil in active
/// cell 2718 in both the oil-gas and oil-water sub-systems while
/// excluding effects of end-point scaling. This effectively
/// retrieves the "raw" tabulated saturation functions in the
/// INIT result set.
///
/// \code
/// using RC = ECLSaturationFunc::RawCurve;
/// auto func = std::vector<RC>{};
/// func.reserve(2);
///
/// // Request krog (oil rel-perm in oil-gas system)
/// func.push_back(RC{
/// RC::Function::RelPerm,
/// RC::SubSystem::OilGas,
/// ECLPhaseIndex::Liquid
/// });
///
/// // Request krow (oil rel-perm in oil-water system)
/// func.push_back(RC{
/// RC::Function::RelPerm,
/// RC::SubSystem::OilWater,
/// ECLPhaseIndex::Liquid
/// });
///
/// const auto graph =
/// sfunc.getSatFuncCurve(func, 2718, false);
/// \endcode
std::vector<FlowDiagnostics::Graph>
getSatFuncCurve(const std::vector<RawCurve>& func,
const int activeCell,
const bool useEPS = true) const;
private:
/// Implementation backend.
class Impl;

View File

@ -0,0 +1,124 @@
/*
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/ECLTableInterpolation1D.hpp>
#include <algorithm>
#include <cassert>
#include <exception>
#include <functional>
#include <stdexcept>
#include <utility>
#include <vector>
namespace Details {
template <class Compare>
std::vector<double>::size_type
intervalInRange(const std::vector<double>& abscissas,
const double x,
Compare&& compare)
{
// Verify that 'x' is within range. Order of arguments matters.
assert (! compare(x, abscissas.front()));
assert (! compare(abscissas.back(), x));
const auto b = std::begin(abscissas);
const auto p =
std::lower_bound(b, std::end(abscissas), x,
std::forward<Compare>(compare));
assert (p != std::end(abscissas));
// p = lower_bound() => p identifies *right-hand* (upper) end-point
// of interval (insertion point) => (p - b) is index of right-hand
// end-point. Consequently (p - b) - 1 is index of *left-hand*
// end-point and the point with which we associate the pertinent
// interval. Special case handling for p == b.
return (p == b) ? 0 : (p - b) - 1;
}
template <class Compare>
Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint
identifyPoint(const std::vector<double>& xi,
const double x,
Compare&& compare)
{
namespace PP = ::Opm::Interp1D::PiecewisePolynomial;
using PCat = ::Opm::Interp1D::PointCategory;
const auto left = xi.front();
if (compare(x, left)) {
// Left of min(xi) (== xi.front())
return PP::LocalInterpPoint {
PCat::LeftOfRange, 0, x - left,
};
}
const auto right = xi.back();
if (compare(right, x)) {
// Right of max(xi) (== xi.back())
return PP::LocalInterpPoint {
PCat::RightOfRange, xi.size() - 1, x - right,
};
}
// Common case: x \in [min(xi), max(xi)]
const auto interval = intervalInRange(xi, x, compare);
assert ((interval + 1 < xi.size()) || (xi.size() == 1));
return PP::LocalInterpPoint {
PCat::InRange, interval, x - xi[interval],
};
}
template <class Compare = std::less<double>>
Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint
identify(const std::vector<double>& xi,
const double x,
Compare&& compare = Compare{})
{
if (xi.empty()) {
throw std::invalid_argument {
"Cannot Relate Point to Non-Existent Range of Variable"
};
}
return identifyPoint(xi, x, std::forward<Compare>(compare));
}
} // Anonymous
// =====================================================================
Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint
Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint::
identify(const std::vector<double>& xi,
const double x, std::true_type)
{
return Details::identify(xi, x);
}
Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint
Opm::Interp1D::PiecewisePolynomial::LocalInterpPoint::
identify(const std::vector<double>& xi,
const double x, std::false_type)
{
return Details::identify(xi, x, std::greater<double>{});
}

View File

@ -0,0 +1,404 @@
/*
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_ECLTABLEINTERPOLATION1D_HEADER_INCLUDED
#define OPM_ECLTABLEINTERPOLATION1D_HEADER_INCLUDED
#include <cassert>
#include <type_traits>
#include <vector>
/// \file
///
/// Common types and policies for one-dimensional (single variate)
/// interpolation of tabulated functions. Mainly intended to support
/// piecewise linear interpolation.
namespace Opm { namespace Interp1D {
/// Categories of interpolation behaviour according to location of
/// interpolation point with respect to range of independent variable.
enum class PointCategory {
/// Point within range of independent variable.
InRange,
/// Point is to the left of range.
LeftOfRange,
/// Point is to the right of range.
RightOfRange,
};
/// Functionality for interpolating functions of a single variate using
/// piecewise polynomials.
namespace PiecewisePolynomial {
/// Policies that implement particular behaviours for extrapolating
/// a tabulated function, assuming ECL's table format, outside the
/// tabulated range of its independent variable.
namespace ExtrapolationPolicy {
/// Extrapolate function using constant values.
class Constant
{
public:
/// Extrapolate function to the left of range using first
/// tabulated function value.
///
/// \tparam TabulatedFunction Representation of a tabulated
/// function. Must support function call operator such
/// that the statement \code y = Func(i, col); \endcode
/// returns the value of the \c col-th dependent variate
/// at the \c i-th abscissa.
///
/// \tparam Index Integer type representing an index into
/// the abscissas of the tabulated function.
///
/// \param[in] xmin Location of left-most abscissa. Unused.
///
/// \param[in] x Interpolation point (global coordinates).
/// Unused.
///
/// \param[in] col Column index identifying which dependent
/// variate to extrapolate.
///
/// \param[in] f Tabulated function instance.
///
/// \return Value of \p col-th variate of tabulated function
/// extrapolated to the left of the range of table's
/// independent variate.
template <class TabulatedFunction, class Index>
double left(const std::vector<double>& /* xi */,
const double /* x */,
const Index col ,
TabulatedFunction&& f) const
{
return f(0, col);
}
/// Extrapolate function to the rigth of range using "last"
/// tabulated function value.
///
/// \tparam TabulatedFunction Representation of a tabulated
/// function. Must support function call operator such
/// that the statement \code y = Func(i, col); \endcode
/// returns the value of the \c col-th dependent variate
/// at the \c i-th abscissa.
///
/// \tparam Index Integer type representing an index into
/// the abscissas of the tabulated function.
///
/// \param[in] xi Location of tabulated function's abscissas
/// (unused).
///
/// \param[in] x Interpolation point (global coordinates).
/// Unused.
///
/// \param[in] col Column index identifying which dependent
/// variate to extrapolate.
///
/// \param[in] f Tabulated function instance.
///
/// \return Value of \p col-th variate of tabulated function
/// extrapolated to the rigth of the range of table's
/// independent variate.
template <class TabulatedFunction, class Index>
double right(const std::vector<double>& xi,
const double /* x */,
const Index col,
TabulatedFunction&& f) const
{
return f(xi.size() - 1, col);
}
};
/// Extrapolate function using extrapolated/estimated
/// derivatives in range end-points.
///
/// Derivatives estimated from table points nearest to the end
/// of the range.
class Linearly
{
public:
/// Extrapolate function to the left of range using first
/// tabulated function value and estimated derivative.
///
/// \tparam TabulatedFunction Representation of a tabulated
/// function. Must support function call operator such
/// that the statement \code y = Func(i, col); \endcode
/// returns the value of the \c col-th dependent variate
/// at the \c i-th abscissa.
///
/// \tparam Index Integer type representing an index into
/// the abscissas of the tabulated function.
///
/// \param[in] xi Location of tabulated function's abscissas.
///
/// \param[in] x Interpolation point (global coordinates).
///
/// \param[in] col Column index identifying which dependent
/// variate to extrapolate.
///
/// \param[in] f Tabulated function instance.
///
/// \return Value of \p col-th variate of tabulated function
/// extrapolated to the left of the range of table's
/// independent variate.
template <class TabulatedFunction, class Index>
double left(const std::vector<double>& xi,
const double x,
const Index col,
TabulatedFunction&& f) const
{
// Derivative of f(i,col)
const auto f0 = f(0, col);
const auto f1 = f(1, col);
const auto dfdx = (f1 - f0) / (xi[1] - xi[0]);
// <= 0 if ascending range.
const auto dx = x - xi.front();
return f0 + (dfdx * dx);
}
/// Extrapolate function to the rigth of range using "last"
/// tabulated function value and estimated derivative.
///
/// \tparam TabulatedFunction Representation of a tabulated
/// function. Must support function call operator such
/// that the statement \code y = Func(i, col); \endcode
/// returns the value of the \c col-th dependent variate
/// at the \c i-th abscissa.
///
/// \tparam Index Integer type representing an index into
/// the abscissas of the tabulated function.
///
/// \param[in] xi Location of tabulated function's abscissas.
///
/// \param[in] x Interpolation point (global coordinates).
///
/// \param[in] col Column index identifying which dependent
/// variate to extrapolate.
///
/// \param[in] f Tabulated function instance.
///
/// \return Value of \p col-th variate of tabulated function
/// extrapolated to the rigth of the range of table's
/// independent variate.
template <class TabulatedFunction, class Index>
double right(const std::vector<double>& xi,
const double x,
const Index col,
TabulatedFunction&& f) const
{
const auto nRows = xi.size();
// Derivative of f(i,col)
const auto f0 = f(nRows - 2, col);
const auto f1 = f(nRows - 1, col);
const auto dfdx =
(f1 - f0) / (xi[nRows - 1] - xi[nRows - 2]);
// >= 0 if ascending range
const auto dx = x - xi.back();
return f1 + (dfdx * dx);
}
};
/// Extrapolate function using tabulated constant derivatives in
/// range end-points.
class LinearlyWithDerivatives
{
public:
/// Constructor
///
/// \param[in] nResCol Number of function value (result)
/// columns in underlying tabulated function. Equal to
/// total number of dependent variable columns less the
/// number of derivative columns. Typically two as in
/// the cases of the PV{D,T}{G,O} tables.
explicit LinearlyWithDerivatives(const std::size_t nResCol)
: nResCol_(nResCol)
{}
/// Extrapolate function to the left of range using first
/// tabulated function value and corresponding derivative.
///
/// \tparam TabulatedFunction Representation of a tabulated
/// function. Must support function call operator such
/// that the statement \code y = Func(i, col); \endcode
/// returns the value of the \c col-th dependent variate
/// at the \c i-th abscissa.
///
/// \tparam Index Integer type representing an index into
/// the abscissas of the tabulated function.
///
/// \param[in] xi Location of tabulated function's abscissas.
///
/// \param[in] x Interpolation point (global coordinates).
///
/// \param[in] col Column index identifying which dependent
/// variate to extrapolate.
///
/// \param[in] f Tabulated function instance.
///
/// \return Value of \p col-th variate of tabulated function
/// extrapolated to the left of the range of table's
/// independent variate.
template <class TabulatedFunction, class Index>
double left(const std::vector<double>& xi,
const double x,
const Index col,
TabulatedFunction&& f) const
{
// Derivative of f(i,col) in f(i, nResCol_ + col)
const auto dfdx = f(0, this->nResCol_ + col);
const auto dx = x - xi.front(); // <= 0 if ascending range
return f(0, col) + (dfdx * dx);
}
/// Extrapolate function to the rigth of range using "last"
/// tabulated function value and corresponding derivative.
///
/// \tparam TabulatedFunction Representation of a tabulated
/// function. Must support function call operator such
/// that the statement \code y = Func(i, col); \endcode
/// returns the value of the \c col-th dependent variate
/// at the \c i-th abscissa.
///
/// \tparam Index Integer type representing an index into
/// the abscissas of the tabulated function.
///
/// \param[in] xi Location of tabulated function's abscissas.
///
/// \param[in] x Interpolation point (global coordinates).
///
/// \param[in] col Column index identifying which dependent
/// variate to extrapolate.
///
/// \param[in] nRows Number of rows (abscissas) in
/// function's underlying table representation.
///
/// \param[in] f Tabulated function instance.
///
/// \return Value of \p col-th variate of tabulated function
/// extrapolated to the rigth of the range of table's
/// independent variate.
template <class TabulatedFunction, class Index>
double right(const std::vector<double>& xi,
const double x,
const Index col,
TabulatedFunction&& f) const
{
const auto nRows = xi.size();
// Derivative of f(i,col) in f(i, nResCol_ + col)
const auto dfdx = f(nRows - 1, this->nResCol_ + col);
const auto dx = x - xi.back(); // >= 0 if ascending range
return f(nRows - 1, col) + (dfdx * dx);
}
private:
/// Number of function value (result) columns in underlying
/// tabulated function.
std::size_t nResCol_;
};
} // ExtrapolationPolicy
/// Interpolation point localized with respect to sequence of
/// non-overlapping intervals with separating abscissas.
struct LocalInterpPoint {
/// Interpolation behaviour of point with respect to range.
PointCategory cat;
/// Interval index. Meaningful only with respect to particular
/// sequence of abscissas. Zero when
///
/// \code
/// cat == PointCategory::LeftOfRange
/// \endcode
///
/// Equal to number of abscissas (i.e., one greater than number
/// of intervals) when
///
/// \code
/// cat == PointCategory::RightOfRange
/// \endcode.
std::vector<double>::size_type interval;
/// Local coordinate within interval. Defined as
///
/// \code
/// x - abscissa[interval]
/// \endcode
///
/// Non-positive for PointCategory::LeftOfRange. Non-negative
/// otherwise.
double t;
/// Identify point category and, usually, particular interval in
/// which a specific point is localized.
///
/// Overload for usual case of ascendingly sorted abscissas.
///
/// \param[in] xi Sequence of separating abscissas representing
/// non-overlapping intervals of an independent variable.
///
/// \param[in] x Sample point.
///
/// \param[in] is_ascending Tagged dispatch overload
/// disambiguation object. Unused.
///
/// \return Sample point localized with respect to the abscissas
/// \p xi.
static LocalInterpPoint
identify(const std::vector<double>& xi,
const double x,
std::true_type is_ascending = std::true_type{});
/// Identify point category and, usually, particular interval in
/// which a specific point is localized.
///
/// Overload for case of descendingly sorted abscissas (e.g.,
/// through \code std::sort(first, last, std::greater<>{})
/// \endcode).
///
/// \param[in] xi Sequence of separating abscissas representing
/// non-overlapping intervals of an independent variable.
///
/// \param[in] x Sample point.
///
/// \param[in] is_ascending Tagged dispatch overload
/// disambiguation object. Unused.
///
/// \return Sample point localized with respect to the abscissas
/// \p xi.
static LocalInterpPoint
identify(const std::vector<double>& xi,
const double x,
std::false_type is_ascending);
};
} // PiecewisePolynomial
}} // Opm::Interp1D
#endif // OPM_ECLTABLEINTERPOLATION1D_HEADER_INCLUDED

View File

@ -42,6 +42,16 @@ namespace Opm { namespace ECLUnits {
class USys<ECL_METRIC_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double density() const override
{
return Metric::Density;
}
virtual double depth() const override
{
return Metric::Length;
}
virtual double pressure() const override
{
return Metric::Pressure;
@ -57,6 +67,16 @@ namespace Opm { namespace ECLUnits {
return Metric::ReservoirVolume;
}
virtual double surfaceVolumeGas() const override
{
return Metric::GasSurfaceVolume;
}
virtual double surfaceVolumeLiquid() const override
{
return Metric::LiquidSurfaceVolume;
}
virtual double time() const override
{
return Metric::Time;
@ -66,12 +86,27 @@ namespace Opm { namespace ECLUnits {
{
return Metric::Transmissibility;
}
virtual double viscosity() const override
{
return Metric::Viscosity;
}
};
template <>
class USys<ECL_FIELD_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double density() const override
{
return Field::Density;
}
virtual double depth() const override
{
return Field::Length;
}
virtual double pressure() const override
{
return Field::Pressure;
@ -87,6 +122,16 @@ namespace Opm { namespace ECLUnits {
return Field::ReservoirVolume;
}
virtual double surfaceVolumeGas() const override
{
return Field::GasSurfaceVolume;
}
virtual double surfaceVolumeLiquid() const override
{
return Field::LiquidSurfaceVolume;
}
virtual double time() const override
{
return Field::Time;
@ -96,12 +141,27 @@ namespace Opm { namespace ECLUnits {
{
return Field::Transmissibility;
}
virtual double viscosity() const override
{
return Field::Viscosity;
}
};
template <>
class USys<ECL_LAB_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double density() const override
{
return Lab::Density;
}
virtual double depth() const override
{
return Lab::Length;
}
virtual double pressure() const override
{
return Lab::Pressure;
@ -117,6 +177,16 @@ namespace Opm { namespace ECLUnits {
return Lab::ReservoirVolume;
}
virtual double surfaceVolumeGas() const override
{
return Lab::GasSurfaceVolume;
}
virtual double surfaceVolumeLiquid() const override
{
return Lab::LiquidSurfaceVolume;
}
virtual double time() const override
{
return Lab::Time;
@ -126,12 +196,30 @@ namespace Opm { namespace ECLUnits {
{
return Lab::Transmissibility;
}
virtual double viscosity() const override
{
return Lab::Viscosity;
}
};
template <>
class USys<ECL_PVT_M_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double density() const override
{
using namespace prefix;
using namespace unit;
return kilogram / cubic(meter);
}
virtual double depth() const override
{
return unit::meter;
}
virtual double pressure() const override
{
return unit::atm;
@ -153,6 +241,16 @@ namespace Opm { namespace ECLUnits {
return cubic(meter);
}
virtual double surfaceVolumeGas() const override
{
return unit::cubic(unit::meter);
}
virtual double surfaceVolumeLiquid() const override
{
return unit::cubic(unit::meter);
}
virtual double time() const override
{
return unit::day;
@ -165,6 +263,11 @@ namespace Opm { namespace ECLUnits {
return centi*Poise * cubic(meter) / (day * atm);
}
virtual double viscosity() const override
{
return prefix::centi*unit::Poise;
}
};
} // namespace Impl
}} // namespace Opm::ECLUnits
@ -183,6 +286,18 @@ Opm::ECLUnits::Impl::getUnitConvention(const int usys)
+ std::to_string(usys));
}
double Opm::ECLUnits::UnitSystem::dissolvedGasOilRat() const
{
return this->surfaceVolumeGas()
/ this->surfaceVolumeLiquid();
}
double Opm::ECLUnits::UnitSystem::vaporisedOilGasRat() const
{
return this->surfaceVolumeLiquid()
/ this->surfaceVolumeGas();
}
std::unique_ptr<const ::Opm::ECLUnits::UnitSystem>
Opm::ECLUnits::createUnitSystem(const int usys)
{

View File

@ -28,11 +28,19 @@ namespace Opm {
struct UnitSystem
{
virtual double pressure() const = 0;
virtual double reservoirRate() const = 0;
virtual double reservoirVolume() const = 0;
virtual double time() const = 0;
virtual double transmissibility() const = 0;
virtual double density() const = 0;
virtual double depth() const = 0;
virtual double pressure() const = 0;
virtual double reservoirRate() const = 0;
virtual double reservoirVolume() const = 0;
virtual double surfaceVolumeLiquid() const = 0;
virtual double surfaceVolumeGas() const = 0;
virtual double time() const = 0;
virtual double transmissibility() const = 0;
virtual double viscosity() const = 0;
double dissolvedGasOilRat() const; // Rs
double vaporisedOilGasRat() const; // Rv
};
std::unique_ptr<const UnitSystem>

View File

@ -20,6 +20,8 @@
#include <examples/exampleSetup.hpp>
#include <opm/utility/ECLCaseUtilities.hpp>
#include <algorithm>
#include <array>
#include <cassert>
@ -213,42 +215,6 @@ namespace {
return max;
}
std::vector<int>
availableReportSteps(const example::FilePaths& paths)
{
using FilePtr = ::ERT::
ert_unique_ptr<ecl_file_type, ecl_file_close>;
const auto rsspec_fn = example::
deriveFileName(paths.grid, { ".RSSPEC", ".FRSSPEC" });
// Read-only, keep open between requests
const auto open_flags = 0;
auto rsspec = FilePtr{
ecl_file_open(rsspec_fn.generic_string().c_str(), open_flags)
};
auto* globView = ecl_file_get_global_view(rsspec.get());
const auto* ITIME_kw = "ITIME";
const auto n = ecl_file_view_get_num_named_kw(globView, ITIME_kw);
auto steps = std::vector<int>(n);
for (auto i = 0*n; i < n; ++i) {
const auto* itime =
ecl_file_view_iget_named_kw(globView, ITIME_kw, i);
const auto* itime_data =
static_cast<const int*>(ecl_kw_iget_ptr(itime, 0));
steps[i] = itime_data[0];
}
return steps;
}
ErrorTolerance
testTolerances(const ::Opm::ParameterGroup& param)
{
@ -278,8 +244,8 @@ namespace {
ReferenceToF
loadReference(const ::Opm::ParameterGroup& param,
const int step,
const int nDigits)
const int step,
const int nDigits)
{
namespace fs = boost::filesystem;
@ -431,7 +397,7 @@ try {
auto setup = example::Setup(argc, argv);
const auto tol = testTolerances(setup.param);
const auto steps = availableReportSteps(setup.file_paths);
const auto steps = setup.result_set.reportStepIDs();
const auto E = sampleDifferences(std::move(setup), steps);
const auto ok =

View File

@ -20,6 +20,8 @@
#include <examples/exampleSetup.hpp>
#include <opm/utility/ECLCaseUtilities.hpp>
#include <algorithm>
#include <array>
#include <cassert>
@ -30,6 +32,7 @@
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <numeric>
#include <sstream>
#include <stdexcept>
@ -340,42 +343,6 @@ namespace {
return max;
}
std::vector<int>
availableReportSteps(const example::FilePaths& paths)
{
using FilePtr = ::ERT::
ert_unique_ptr<ecl_file_type, ecl_file_close>;
const auto rsspec_fn = example::
deriveFileName(paths.grid, { ".RSSPEC", ".FRSSPEC" });
// Read-only, keep open between requests
const auto open_flags = 0;
auto rsspec = FilePtr{
ecl_file_open(rsspec_fn.generic_string().c_str(), open_flags)
};
auto* globView = ecl_file_get_global_view(rsspec.get());
const auto* ITIME_kw = "ITIME";
const auto n = ecl_file_view_get_num_named_kw(globView, ITIME_kw);
auto steps = std::vector<int>(n);
for (auto i = 0*n; i < n; ++i) {
const auto* itime =
ecl_file_view_iget_named_kw(globView, ITIME_kw, i);
const auto* itime_data =
static_cast<const int*>(ecl_kw_iget_ptr(itime, 0));
steps[i] = itime_data[0];
}
return steps;
}
ErrorTolerance
testTolerances(const ::Opm::ParameterGroup& param)
{
@ -412,9 +379,9 @@ namespace {
ReferenceSolution
loadReference(const ::Opm::ParameterGroup& param,
const std::string& quant,
const int step,
const int nDigits)
const std::string& quant,
const int step,
const int nDigits)
{
namespace fs = boost::filesystem;
@ -491,12 +458,21 @@ namespace {
E.relative.push_back(std::move(rel));
}
std::unique_ptr<Opm::ECLRestartData>
openRestartSet(const Opm::ECLCaseUtilities::ResultSet& rset,
const int step)
{
return std::unique_ptr<Opm::ECLRestartData> {
new Opm::ECLRestartData(rset.restartFile(step))
};
}
std::array<AggregateErrors, 2>
sampleDifferences(const ::Opm::ECLGraph& graph,
const ::Opm::ECLRestartData& rstrt,
const ::Opm::ParameterGroup& param,
const std::string& quant,
const std::vector<int>& steps)
sampleDifferences(const ::Opm::ECLGraph& graph,
const ::Opm::ECLCaseUtilities::ResultSet& rset,
const ::Opm::ParameterGroup& param,
const std::string& quant,
const std::vector<int>& steps)
{
const auto ECLquant = boost::algorithm::to_upper_copy(quant);
@ -508,10 +484,18 @@ namespace {
const auto nDigits = numDigits(steps);
auto rstrt = std::unique_ptr<Opm::ECLRestartData>{};
auto E = std::array<AggregateErrors, 2>{};
for (const auto& step : steps) {
if (! rstrt.selectReportStep(step)) {
if (! (rset.isUnifiedRestart() && bool(rstrt))) {
// Separate (not unified) restart file or this is the first
// time we're selecting a report step.
rstrt = openRestartSet(rset, step);
}
if (! rstrt->selectReportStep(step)) {
continue;
}
@ -519,7 +503,7 @@ namespace {
{
const auto raw = Calculated {
graph.rawLinearisedCellData<double>(rstrt, ECLquant)
graph.rawLinearisedCellData<double>(*rstrt, ECLquant)
};
computeErrors(Reference{ ref.raw }, raw, E[0]);
@ -527,7 +511,7 @@ namespace {
{
const auto SI = Calculated {
graph.linearisedCellData(rstrt, ECLquant, unit)
graph.linearisedCellData(*rstrt, ECLquant, unit)
};
computeErrors(Reference{ ref.SI }, SI, E[1]);
@ -556,28 +540,27 @@ namespace {
}
::Opm::ECLGraph
constructGraph(const example::FilePaths& pth)
constructGraph(const Opm::ECLCaseUtilities::ResultSet& rset)
{
const auto I = ::Opm::ECLInitFileData(pth.init);
const auto I = ::Opm::ECLInitFileData(rset.initFile());
return ::Opm::ECLGraph::load(pth.grid, I);
return ::Opm::ECLGraph::load(rset.gridFile(), I);
}
} // namespace Anonymous
int main(int argc, char* argv[])
try {
const auto prm = example::initParam(argc, argv);
const auto pth = example::FilePaths(prm);
const auto tol = testTolerances(prm);
const auto prm = example::initParam(argc, argv);
const auto rset = example::identifyResultSet(prm);
const auto tol = testTolerances(prm);
const auto rstrt = ::Opm::ECLRestartData(pth.restart);
const auto steps = availableReportSteps(pth);
const auto graph = constructGraph(pth);
const auto steps = rset.reportStepIDs();
const auto graph = constructGraph(rset);
auto all_ok = true;
for (const auto& quant : testQuantities(prm)) {
const auto E =
sampleDifferences(graph, rstrt, prm, quant, steps);
sampleDifferences(graph, rset, prm, quant, steps);
const auto ok =
everythingFine(E[0], tol) && everythingFine(E[1], tol);

View File

@ -18,10 +18,11 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <opm/utility/ECLGraph.hpp>
#include <examples/exampleSetup.hpp>
#include <opm/utility/ECLCaseUtilities.hpp>
#include <opm/utility/ECLGraph.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
@ -186,7 +187,7 @@ namespace {
}
bool transfieldAcceptable(const ::Opm::ParameterGroup& param,
const std::vector<double>& trans)
const std::vector<double>& trans)
{
const auto Tref = loadReference(param);
@ -208,21 +209,21 @@ namespace {
}
::Opm::ECLGraph
constructGraph(const example::FilePaths& pth)
constructGraph(const Opm::ECLCaseUtilities::ResultSet& rset)
{
const auto I = ::Opm::ECLInitFileData(pth.init);
const auto I = ::Opm::ECLInitFileData(rset.initFile());
return ::Opm::ECLGraph::load(pth.grid, I);
return ::Opm::ECLGraph::load(rset.gridFile(), I);
}
} // namespace Anonymous
int main(int argc, char* argv[])
try {
const auto prm = example::initParam(argc, argv);
const auto pth = example::FilePaths(prm);
const auto G = constructGraph(pth);
const auto T = G.transmissibility();
const auto ok = transfieldAcceptable(prm, T);
const auto prm = example::initParam(argc, argv);
const auto rset = example::identifyResultSet(prm);
const auto G = constructGraph(rset);
const auto T = G.transmissibility();
const auto ok = transfieldAcceptable(prm, T);
std::cout << (ok ? "OK" : "FAIL") << '\n';

View File

@ -99,7 +99,6 @@ BOOST_AUTO_TEST_CASE (NoScaling)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.0,
0.2,
@ -111,9 +110,21 @@ BOOST_AUTO_TEST_CASE (NoScaling)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
check_is_close(s_inp, s);
}
}
BOOST_AUTO_TEST_CASE (ScaledConnate)
@ -136,7 +147,6 @@ BOOST_AUTO_TEST_CASE (ScaledConnate)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
0,
@ -148,9 +158,30 @@ BOOST_AUTO_TEST_CASE (ScaledConnate)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
const auto s_inp_expect = std::vector<double> {
0.2, // t.s <= smin => smin
0.2, // t.s <= smin => smin
0.4,
0.6,
0.8,
1.0,
};
check_is_close(s_inp, s_inp_expect);
}
}
BOOST_AUTO_TEST_CASE (ScaledMax)
@ -173,7 +204,6 @@ BOOST_AUTO_TEST_CASE (ScaledMax)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
0.25,
@ -185,9 +215,30 @@ BOOST_AUTO_TEST_CASE (ScaledMax)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
const auto s_inp_expect = std::vector<double> {
0.0,
0.2,
0.4,
0.6,
0.8,
0.8, // t.s >= smax => smax
};
check_is_close(s_inp, s_inp_expect);
}
}
BOOST_AUTO_TEST_CASE (ScaledBoth)
@ -210,7 +261,6 @@ BOOST_AUTO_TEST_CASE (ScaledBoth)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
0.0,
@ -222,9 +272,30 @@ BOOST_AUTO_TEST_CASE (ScaledBoth)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
const auto s_inp_expect = std::vector<double> {
0.2, // t.s <= smin => smin
0.2,
0.4,
0.6,
0.8,
0.8, // t.s >= smax => smax
};
check_is_close(s_inp, s_inp_expect);
}
}
BOOST_AUTO_TEST_SUITE_END ()
@ -252,7 +323,6 @@ BOOST_AUTO_TEST_CASE (NoScaling)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.2,
0.2,
@ -264,9 +334,21 @@ BOOST_AUTO_TEST_CASE (NoScaling)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
check_is_close(s_inp, expect);
}
}
BOOST_AUTO_TEST_CASE (ScaledConnate)
@ -290,7 +372,6 @@ BOOST_AUTO_TEST_CASE (ScaledConnate)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.20,
0.32,
@ -302,9 +383,21 @@ BOOST_AUTO_TEST_CASE (ScaledConnate)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
check_is_close(s_inp, s);
}
}
BOOST_AUTO_TEST_CASE (ScaledMax)
@ -328,7 +421,6 @@ BOOST_AUTO_TEST_CASE (ScaledMax)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.20,
0.20,
@ -340,9 +432,30 @@ BOOST_AUTO_TEST_CASE (ScaledMax)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
const auto s_inp_expect = std::vector<double> {
0.2, // t.s <= smin -> smin
0.2,
0.4,
0.6,
0.8,
1.0,
};
check_is_close(s_inp, s_inp_expect);
}
}
BOOST_AUTO_TEST_CASE (ScaledBoth)
@ -366,7 +479,6 @@ BOOST_AUTO_TEST_CASE (ScaledBoth)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.2,
0.2,
@ -378,9 +490,30 @@ BOOST_AUTO_TEST_CASE (ScaledBoth)
const auto eps = SF::TwoPointScaling{ smin, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
const auto s_inp_expect = std::vector<double> {
0.5, // t.s <= smin -> smin
0.5, // t.s <= smin -> smin
0.5, // t.s <= smin -> smin
0.6,
0.7, // t.s >= smax -> smax
0.7, // t.s >= smax -> smax
};
check_is_close(s_inp, s_inp_expect);
}
}
BOOST_AUTO_TEST_SUITE_END ()
@ -411,7 +544,6 @@ BOOST_AUTO_TEST_CASE (NoScaling)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0.0,
0.2,
@ -423,9 +555,21 @@ BOOST_AUTO_TEST_CASE (NoScaling)
const auto eps = SF::ThreePointScaling{ smin, sdisp, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
check_is_close(s_inp, s);
}
}
BOOST_AUTO_TEST_CASE (ScaledConnate)
@ -449,7 +593,6 @@ BOOST_AUTO_TEST_CASE (ScaledConnate)
1.0,
};
const auto sp = associate(s);
const auto expect = std::vector<double> {
0,
1.0 / 15,
@ -461,9 +604,30 @@ BOOST_AUTO_TEST_CASE (ScaledConnate)
const auto eps = SF::ThreePointScaling{ smin, sdisp, smax };
const auto s_eff = eps.eval(tep, sp);
// Input saturation -> Scaled saturation
{
const auto sp = associate(s);
const auto s_eff = eps.eval(tep, sp);
check_is_close(s_eff, expect);
check_is_close(s_eff, expect);
}
// Tabulated saturation -> Input saturation
{
const auto sp = associate(expect);
const auto s_inp = eps.reverse(tep, sp);
const auto s_inp_expect = std::vector<double> {
0.1, // t.s <= smin -> smin
0.2,
0.4,
0.6,
0.8,
1.0,
};
check_is_close(s_inp, s_inp_expect);
}
}
BOOST_AUTO_TEST_SUITE_END ()

View File

@ -81,6 +81,19 @@ namespace {
return t;
}
Opm::SatFuncInterpolant::ConvertUnits
createDummyUnitConverter(const std::size_t ncol)
{
using Cvrt = Opm::SatFuncInterpolant::ConvertUnits::Converter;
auto id = [](const double x) { return x; };
return Opm::SatFuncInterpolant::ConvertUnits {
Cvrt{ id },
std::vector<Cvrt>(ncol, Cvrt{ id })
};
}
} // Namespace Anonymous
// =====================================================================
@ -97,11 +110,13 @@ BOOST_AUTO_TEST_CASE (EmptyTable)
// s, kr , pc
};
t.numRows = 0;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 0;
t.numCols = 3;
t.numTables = 1;
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t)),
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t),
createDummyUnitConverter(2)),
std::invalid_argument);
}
@ -114,11 +129,13 @@ BOOST_AUTO_TEST_CASE (SingleNode)
0.3 , 0.1 , 0.0,
};
t.numRows = 1;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 1;
t.numCols = 3;
t.numTables = 1;
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t)),
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t),
createDummyUnitConverter(2)),
std::invalid_argument);
}
@ -134,11 +151,13 @@ BOOST_AUTO_TEST_CASE (NoResultColumns)
0.8,
};
t.numRows = 4;
t.numCols = 1;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 1;
t.numTables = 1;
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t)),
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t),
createDummyUnitConverter(0)),
std::invalid_argument);
}
@ -158,11 +177,13 @@ BOOST_AUTO_TEST_CASE (EmptyTableLargeNodeAlloc)
1.0e+20 , 1.0e+20, 0.0,
};
t.numRows = 8;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 8;
t.numCols = 3;
t.numTables = 1;
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t)),
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t),
createDummyUnitConverter(2)),
std::invalid_argument);
}
@ -183,11 +204,13 @@ BOOST_AUTO_TEST_CASE (SingleNodeLargeNodeAlloc)
1.0e+20 , 1.0e+20, 0.0,
};
t.numRows = 9;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 9;
t.numCols = 3;
t.numTables = 1;
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t)),
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t),
createDummyUnitConverter(2)),
std::invalid_argument);
}
@ -211,11 +234,13 @@ BOOST_AUTO_TEST_CASE (NoResultColumnsLargeNodeAlloc)
1.0e+20,
};
t.numRows = 12;
t.numCols = 1;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 12;
t.numCols = 1;
t.numTables = 1;
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t)),
BOOST_CHECK_THROW(Opm::SatFuncInterpolant(toRawTableFormat(t),
createDummyUnitConverter(2)),
std::invalid_argument);
}
@ -238,14 +263,18 @@ BOOST_AUTO_TEST_CASE (AtNodes)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{ 0.8, 0.3, 0.3, 0.2 };
const auto kr_expect = std::vector<double>{ 0.5, 0.1, 0.1, 0.0 };
@ -284,14 +313,18 @@ BOOST_AUTO_TEST_CASE (AboveAndBelow)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{ 0.80000001, 0.9, 0.199999999, 0.1 };
const auto kr_expect = std::vector<double>{ 0.5, 0.5, 0.0, 0.0 };
@ -318,14 +351,18 @@ BOOST_AUTO_TEST_CASE (Interpolation)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{
0.2000,
@ -411,14 +448,18 @@ BOOST_AUTO_TEST_CASE (InterpolationLargeNodeAlloc)
1.0e20 , 1.0e+100 , 0.0, // 15
};
t.numRows = 15;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 15;
t.numCols = 3;
t.numTables = 1;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{
0.0000,
@ -534,14 +575,18 @@ BOOST_AUTO_TEST_CASE (AtNodes)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{ 0.8, 0.3, 0.3, 0.2 };
const auto kr_expect = std::vector<double>{ 0.5, 0.1, 0.1, 0.0 };
@ -599,14 +644,18 @@ BOOST_AUTO_TEST_CASE (AboveAndBelow)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{ 0.80000001, 0.9, 0.199999999, 0.1 };
const auto kr_expect = std::vector<double>{ 0.5, 0.5, 0.0, 0.0 };
@ -655,14 +704,18 @@ BOOST_AUTO_TEST_CASE (Interpolation)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{
0.2000,
@ -805,14 +858,18 @@ BOOST_AUTO_TEST_CASE (InterpolationLargeNodeAlloc)
1.0e20 , 1.0e+100 , 0.0, // 15
};
t.numRows = 15;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 15;
t.numCols = 3;
t.numTables = 4;
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
const auto s = std::vector<double>{
0.0000,
@ -911,9 +968,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -923,7 +981,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -960,9 +1021,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn_LargeNodeAlloc)
1.0e20 , 1.0e+100 , 0.0, // 15
};
t.numRows = 15;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 15;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -972,7 +1034,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -997,9 +1062,10 @@ BOOST_AUTO_TEST_CASE (SWFN)
0.8 , 0.5 , 0.0,
};
t.numRows = 4;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1009,7 +1075,10 @@ BOOST_AUTO_TEST_CASE (SWFN)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1047,9 +1116,10 @@ BOOST_AUTO_TEST_CASE (SWFN_LargeNodeAlloc)
1.0e20 , 1.0e+100 , 0.0, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1059,7 +1129,10 @@ BOOST_AUTO_TEST_CASE (SWFN_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1083,9 +1156,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn)
0.8 , 0.5 , 0.8,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1096,7 +1170,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1134,9 +1211,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 15
};
t.numRows = 15;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 15;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1147,7 +1225,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1174,9 +1255,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn)
0.8 , 0.5 , 0.8,
};
t.numRows = 4;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1187,7 +1269,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1226,9 +1311,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1239,7 +1325,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1266,9 +1355,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn)
0.8 , 0.5 , 0.8,
};
t.numRows = 4;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1279,7 +1369,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1318,9 +1411,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1331,7 +1425,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1360,9 +1457,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn)
0.8 , 0.5 , 0.8,
};
t.numRows = 6;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 6;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1373,7 +1471,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1412,9 +1513,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 1;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 };
@ -1425,7 +1527,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1474,9 +1579,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn)
0.8 , 0.5 , 0.0,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2, 0.2, 0.2, 0.2 };
@ -1486,7 +1592,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1574,9 +1683,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn_LargeNodeAlloc)
1.0e20 , 1.0e+100 , 0.0, // 15
};
t.numRows = 15;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 15;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2, 0.2, 0.2, 0.2 };
@ -1586,7 +1696,10 @@ BOOST_AUTO_TEST_CASE (SWFN_CritIsConn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1629,9 +1742,10 @@ BOOST_AUTO_TEST_CASE (SWFN)
0.8 , 0.5 , 0.0,
};
t.numRows = 4;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.2 , 0.2 , 0.2 };
@ -1641,7 +1755,10 @@ BOOST_AUTO_TEST_CASE (SWFN)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1733,9 +1850,10 @@ BOOST_AUTO_TEST_CASE (SWFN_LargeNodeAlloc)
1.0e20 , 1.0e+100 , 0.0, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.1 , 0.1 , 0.0 };
@ -1745,7 +1863,10 @@ BOOST_AUTO_TEST_CASE (SWFN_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1784,9 +1905,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn)
0.8 , 0.5 , 0.8,
};
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 3;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2, 0.2, 0.2, 0.2 };
@ -1797,7 +1919,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1886,9 +2011,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 15
};
t.numRows = 15;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 15;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2, 0.1, 0.0, 0.1 };
@ -1899,7 +2025,10 @@ BOOST_AUTO_TEST_CASE (SOF3_CritIsConn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -1944,9 +2073,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn)
0.8 , 0.5 , 0.8,
};
t.numRows = 4;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.2 , 0.2 , 0.2 };
@ -1957,7 +2087,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -2050,9 +2183,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.2 , 0.1 , 0.2 };
@ -2063,7 +2197,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOGCR_is_Conn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -2108,9 +2245,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn)
0.8 , 0.5 , 0.8,
};
t.numRows = 4;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 4;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.2 , 0.2 , 0.2 };
@ -2121,7 +2259,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -2214,9 +2355,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.2 , 0.0 , 0.2 };
@ -2227,7 +2369,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SOWCR_is_Conn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -2280,9 +2425,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn)
0.9 , 0.8 , 0.9,
};
t.numRows = 6;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 6;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.2 , 0.2 , 0.2 };
@ -2293,7 +2439,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;
@ -2386,9 +2535,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn_LargeNodeAlloc)
1.0e20 , 1.0e+100, 1.0e+100, // 16
};
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
t.numPrimary = 1;
t.numRows = 16;
t.numCols = 3;
t.numTables = 4;
// Table end-points
const auto sconn_expect = std::vector<double>{ 0.2 , 0.1 , 0.2 , 0.2 };
@ -2399,7 +2549,10 @@ BOOST_AUTO_TEST_CASE (SOF3_SCR_Not_Conn_LargeNodeAlloc)
// Note: Need to convert input table to column major (Fortran) order
// because that is the format in which PropTable1D expects the tabular
// data.
const auto swfunc = Opm::SatFuncInterpolant(toRawTableFormat(t));
const auto swfunc = Opm::SatFuncInterpolant {
toRawTableFormat(t),
createDummyUnitConverter(t.numCols - 1)
};
using ResultColumn = Opm::SatFuncInterpolant::ResultColumn;

View File

@ -0,0 +1,544 @@
/*
Copyright 2017 SINTEF ICT, Applied Mathematics.
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_ECLPVTCOMMON_UNITCONV
#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/ECLPvtCommon.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <exception>
#include <stdexcept>
struct ConvertToSI
{
explicit ConvertToSI(const ::Opm::ECLUnits::UnitSystem& usys);
double dens { 0.0 };
double press { 0.0 };
double compr { 0.0 };
double disgas { 0.0 };
double vapoil { 0.0 };
double recipFvf { 0.0 };
double recipFvfDerivPress { 0.0 };
double recipFvfDerivVapOil { 0.0 };
double recipFvfVisc { 0.0 };
double recipFvfViscDerivPress { 0.0 };
double recipFvfViscDerivVapOil { 0.0 };
double recipFvfGas { 0.0 };
double recipFvfGasDerivPress { 0.0 };
double recipFvfGasDerivVapOil { 0.0 };
double recipFvfGasVisc { 0.0 };
double recipFvfGasViscDerivPress { 0.0 };
double recipFvfGasViscDerivVapOil { 0.0 };
};
ConvertToSI::ConvertToSI(const ::Opm::ECLUnits::UnitSystem& usys)
{
using Cvrt = ::Opm::ECLPVT::CreateUnitConverter::ToSI;
auto apply = [](const ::Opm::ECLPVT::ConvertUnits::Converter& cnv)
{
return cnv(1.0);
};
// Mass density
this->dens = apply(Cvrt::density(usys));
// Pressure
this->press = apply(Cvrt::pressure(usys));
// Compressibility
this->compr = apply(Cvrt::compressibility(usys));
// Dissolved gas-oil ratio (Rs)
this->disgas = apply(Cvrt::disGas(usys));
// Vaporised oil-gas ratio (Rv)
this->vapoil = apply(Cvrt::vapOil(usys));
// Reciprocal formation volume factor (1/B)
this->recipFvf = apply(Cvrt::recipFvf(usys));
// Derivative of reciprocal formation volume factor (1/B) with respect
// to fluid (phase) pressure.
this->recipFvfDerivPress =
apply(Cvrt::recipFvfDerivPress(usys));
// Derivative of reciprocal formation volume factor (1/B) with respect
// to vaporised oil-gas ratio.
this->recipFvfDerivVapOil =
apply(Cvrt::recipFvfDerivVapOil(usys));
// Reciprocal product of formation volume factor and viscosity
// (1/(B*mu)).
this->recipFvfVisc = apply(Cvrt::recipFvfVisc(usys));
// Derivative of reciprocal product of formation volume factor and
// viscosity (1/(B*mu)) with respect to fluid (phase) pressure.
this->recipFvfViscDerivPress =
apply(Cvrt::recipFvfViscDerivPress(usys));
// Derivative of reciprocal product of formation volume factor and
// viscosity (1/(B*mu)) with respect to vaporised oil-gas ratio.
this->recipFvfViscDerivVapOil =
apply(Cvrt::recipFvfViscDerivVapOil(usys));
// Reciprocal formation volume factor for gas (1/Bg)
this->recipFvfGas = apply(Cvrt::recipFvfGas(usys));
// Derivative of reciprocal formation volume factor for gas (1/Bg) with
// respect to fluid (phase) pressure.
this->recipFvfGasDerivPress =
apply(Cvrt::recipFvfGasDerivPress(usys));
// Derivative of reciprocal formation volume factor for gas (1/Bg) with
// respect to vaporised oil-gas ratio.
this->recipFvfGasDerivVapOil =
apply(Cvrt::recipFvfGasDerivVapOil(usys));
// Reciprocal product of formation volume factor for gas and viscosity
// (1/(Bg*mu_g)).
this->recipFvfGasVisc = apply(Cvrt::recipFvfGasVisc(usys));
// Derivative of reciprocal product of formation volume factor for gas
// and viscosity (1/(Bg*mu_g)) with respect to fluid (phase) pressure.
this->recipFvfGasViscDerivPress =
apply(Cvrt::recipFvfGasViscDerivPress(usys));
// Derivative of reciprocal product of formation volume factor for gas
// and viscosity (1/(Bg*mu_g)) with respect to vaporised oil-gas ratio.
this->recipFvfGasViscDerivVapOil =
apply(Cvrt::recipFvfGasViscDerivVapOil(usys));
}
template <std::size_t N>
using DVec = ::Opm::ECLPVT::DenseVector<N>;
// =====================================================================
BOOST_AUTO_TEST_SUITE (Basic_Conversion)
BOOST_AUTO_TEST_CASE (Metric)
{
const auto usys = ::Opm::ECLUnits::createUnitSystem(1);
const auto scale = ConvertToSI(*usys);
// Mass density
BOOST_CHECK_CLOSE(scale.dens, 1.0, 1.0e-10);
// Pressure
BOOST_CHECK_CLOSE(scale.press, 1.0e5, 1.0e-10);
// Compressibility
BOOST_CHECK_CLOSE(scale.compr, 1.0e-5, 1.0e-10);
// Dissolved Gas-Oil Ratio (Rs)
BOOST_CHECK_CLOSE(scale.disgas, 1.0, 1.0e-10);
// Vaporised Oil-Gas Ratio (Rv)
BOOST_CHECK_CLOSE(scale.vapoil, 1.0, 1.0e-10);
// Reciprocal Formation Volume Factor (1 / B)
BOOST_CHECK_CLOSE(scale.recipFvf, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfDerivPress, 1.0e-5, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfDerivVapOil, 1.0, 1.0e-10);
// Reciprocal Product of FVF and Viscosity (1 / (B*mu)).
BOOST_CHECK_CLOSE(scale.recipFvfVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivPress, 1.0e-2, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivVapOil, 1.0e3, 1.0e-10);
// Reciprocal Formation Volume Factor for Gas (1 / Bg)
BOOST_CHECK_CLOSE(scale.recipFvfGas, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivPress, 1.0e-5, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg ) w.r.t. Vaporised
// Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivVapOil, 1.0, 1.0e-10);
// Reciprocal Product of FVF for Gas and Viscosity (1 / (Bg*mu_g)).
BOOST_CHECK_CLOSE(scale.recipFvfGasVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivPress, 1.0e-2, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivVapOil, 1.0e3, 1.0e-10);
}
BOOST_AUTO_TEST_CASE (Field)
{
const auto usys = ::Opm::ECLUnits::createUnitSystem(2);
const auto scale = ConvertToSI(*usys);
// Mass density
BOOST_CHECK_CLOSE(scale.dens, 1.601846337396014e+01, 1.0e-10);
// Pressure
BOOST_CHECK_CLOSE(scale.press, 6.894757293168360e+03, 1.0e-10);
// Compressibility
BOOST_CHECK_CLOSE(scale.compr, 1.450377377302092e-04, 1.0e-10);
// Dissolved Gas-Oil Ratio (Rs)
BOOST_CHECK_CLOSE(scale.disgas, 1.781076066790352e+02, 1.0e-10);
// Vaporised Oil-Gas Ratio (Rv)
BOOST_CHECK_CLOSE(scale.vapoil, 5.614583333333335e-03, 1.0e-10);
// Reciprocal Formation Volume Factor (1 / B)
BOOST_CHECK_CLOSE(scale.recipFvf, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfDerivPress,
1.450377377302092e-04, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfDerivVapOil,
1.781076066790352e+02, 1.0e-10);
// Reciprocal Product of FVF and Viscosity (1 / (B*mu)).
BOOST_CHECK_CLOSE(scale.recipFvfVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivPress,
1.450377377302093e-01, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivVapOil,
1.781076066790352e+05, 1.0e-10);
// Reciprocal Formation Volume Factor for Gas (1 / Bg)
BOOST_CHECK_CLOSE(scale.recipFvfGas,
1.781076066790352e+02, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivPress,
2.583232434526917e-02, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg ) w.r.t. Vaporised
// Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivVapOil,
3.172231955693390e+04, 1.0e-10);
// Reciprocal Product of FVF for Gas and Viscosity (1 / (Bg*mu_g)).
BOOST_CHECK_CLOSE(scale.recipFvfGasVisc,
1.781076066790352e+05, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivPress,
2.583232434526917e+01, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivVapOil,
3.172231955693390e+07, 1.0e-10);
}
BOOST_AUTO_TEST_CASE (Lab)
{
const auto usys = ::Opm::ECLUnits::createUnitSystem(3);
const auto scale = ConvertToSI(*usys);
// Mass density
BOOST_CHECK_CLOSE(scale.dens, 1.0e3, 1.0e-10);
// Pressure
BOOST_CHECK_CLOSE(scale.press, 101.325e3, 1.0e-10);
// Compressibility
BOOST_CHECK_CLOSE(scale.compr, 9.869232667160129e-06, 1.0e-10);
// Dissolved Gas-Oil Ratio (Rs)
BOOST_CHECK_CLOSE(scale.disgas, 1.0, 1.0e-10);
// Vaporised Oil-Gas Ratio (Rv)
BOOST_CHECK_CLOSE(scale.vapoil, 1.0, 1.0e-10);
// Reciprocal Formation Volume Factor (1 / B)
BOOST_CHECK_CLOSE(scale.recipFvf, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfDerivPress,
9.869232667160129e-06, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfDerivVapOil,
1.0, 1.0e-10);
// Reciprocal Product of FVF and Viscosity (1 / (B*mu)).
BOOST_CHECK_CLOSE(scale.recipFvfVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivPress,
9.869232667160128e-03, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivVapOil,
1.0e3, 1.0e-10);
// Reciprocal Formation Volume Factor for Gas (1 / Bg)
BOOST_CHECK_CLOSE(scale.recipFvfGas, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivPress,
9.869232667160129e-06, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg) w.r.t. Vaporised
// Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivVapOil, 1.0, 1.0e-10);
// Reciprocal Product of FVF for Gas and Viscosity (1 / (Bg*mu_g)).
BOOST_CHECK_CLOSE(scale.recipFvfGasVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivPress,
9.869232667160128e-03, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivVapOil, 1.0e3, 1.0e-10);
}
BOOST_AUTO_TEST_CASE (PVT_M)
{
const auto usys = ::Opm::ECLUnits::createUnitSystem(4);
const auto scale = ConvertToSI(*usys);
// Mass density
BOOST_CHECK_CLOSE(scale.dens, 1.0, 1.0e-10);
// Pressure
BOOST_CHECK_CLOSE(scale.press, 101.325e3, 1.0e-10);
// Compressibility
BOOST_CHECK_CLOSE(scale.compr, 9.869232667160129e-06, 1.0e-10);
// Dissolved Gas-Oil Ratio (Rs)
BOOST_CHECK_CLOSE(scale.disgas, 1.0, 1.0e-10);
// Vaporised Oil-Gas Ratio (Rv)
BOOST_CHECK_CLOSE(scale.vapoil, 1.0, 1.0e-10);
// Reciprocal Formation Volume Factor (1 / B)
BOOST_CHECK_CLOSE(scale.recipFvf, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfDerivPress,
9.869232667160129e-06, 1.0e-10);
// Derivative of Reciprocal FVF (1 / B) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfDerivVapOil, 1.0, 1.0e-10);
// Reciprocal Product of FVF and Viscosity (1 / (B*mu)).
BOOST_CHECK_CLOSE(scale.recipFvfVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivPress,
9.869232667160128e-03, 1.0e-10);
// Derivative of Reciprocal Product of FVF and Viscosity (1 / (B*mu))
// w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfViscDerivVapOil, 1.0e3, 1.0e-10);
// Reciprocal Formation Volume Factor for Gas (1 / Bg)
BOOST_CHECK_CLOSE(scale.recipFvfGas, 1.0, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivPress,
9.869232667160129e-06, 1.0e-10);
// Derivative of Reciprocal FVF for Gas (1 / Bg ) w.r.t. Vaporised
// Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasDerivVapOil, 1.0, 1.0e-10);
// Reciprocal Product of FVF for Gas and Viscosity (1 / (Bg*mu_g)).
BOOST_CHECK_CLOSE(scale.recipFvfGasVisc, 1.0e3, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Pressure
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivPress,
9.869232667160128e-03, 1.0e-10);
// Derivative of Reciprocal Product of FVF for Gas and Viscosity (1 /
// (Bg*mu_g)) w.r.t. Vaporised Oil-Gas Ratio.
BOOST_CHECK_CLOSE(scale.recipFvfGasViscDerivVapOil, 1.0e3, 1.0e-10);
}
BOOST_AUTO_TEST_SUITE_END ()
// =====================================================================
BOOST_AUTO_TEST_SUITE (DenseVector)
BOOST_AUTO_TEST_CASE (Construct)
{
// DenseVector<1>
{
const auto x = DVec<1>{ std::array<double, 1>{ { 1.0 } } };
BOOST_CHECK_CLOSE(x.array()[0], 1.0, 1.0e-10);
}
// DenseVector<2>
{
const auto x = DVec<2>{ std::array<double, 2>{ { 2.0, -1.0 } } };
BOOST_CHECK_CLOSE(x.array()[0], 2.0, 1.0e-10);
BOOST_CHECK_CLOSE(x.array()[1], -1.0, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (Addition)
{
const auto x = DVec<2>{
std::array<double,2>{ 0.1, 2.3 }
};
const auto two_x = x + x;
BOOST_CHECK_CLOSE(two_x.array()[0], 0.2, 1.0e-10);
BOOST_CHECK_CLOSE(two_x.array()[1], 4.6, 1.0e-10);
}
BOOST_AUTO_TEST_CASE (Subtraction)
{
const auto x = DVec<2>{
std::array<double,2>{ 0.1, 2.3 }
};
const auto y = DVec<2>{
std::array<double,2>{ 10.9, 8.7 }
};
const auto x_minus_y = x - y;
BOOST_CHECK_CLOSE(x_minus_y.array()[0], -10.8, 1.0e-10);
BOOST_CHECK_CLOSE(x_minus_y.array()[1], - 6.4, 1.0e-10);
}
BOOST_AUTO_TEST_CASE (Mult_By_Scalar)
{
// x *= a
{
auto x = DVec<2> {
std::array<double,2>{ 0.1, 2.3 }
};
x *= 5.0;
BOOST_CHECK_CLOSE(x.array()[0], 0.5, 1.0e-10);
BOOST_CHECK_CLOSE(x.array()[1], 11.5, 1.0e-10);
}
// y <- x * a
{
const auto x = DVec<2> {
std::array<double,2>{ 0.1, 2.3 }
};
{
const auto y = x * 5.0;
BOOST_CHECK_CLOSE(y.array()[0], 0.5, 1.0e-10);
BOOST_CHECK_CLOSE(y.array()[1], 11.5, 1.0e-10);
}
{
const auto y = 2.5 * x;
BOOST_CHECK_CLOSE(y.array()[0], 0.25, 1.0e-10);
BOOST_CHECK_CLOSE(y.array()[1], 5.75, 1.0e-10);
}
}
}
BOOST_AUTO_TEST_CASE (Divide_By_Scalar)
{
// x /= a
{
auto x = DVec<2> {
std::array<double,2>{ 0.5, 11.5 }
};
x /= 5.0;
BOOST_CHECK_CLOSE(x.array()[0], 0.1, 1.0e-10);
BOOST_CHECK_CLOSE(x.array()[1], 2.3, 1.0e-10);
}
// y <- x / a
{
const auto x = DVec<2> {
std::array<double,2>{ 0.25, 5.75 }
};
const auto y = x / 2.5;
BOOST_CHECK_CLOSE(y.array()[0], 0.1, 1.0e-10);
BOOST_CHECK_CLOSE(y.array()[1], 2.3, 1.0e-10);
}
}
BOOST_AUTO_TEST_SUITE_END ()

View File

@ -0,0 +1,302 @@
/*
Copyright 2017 SINTEF ICT, Applied Mathematics.
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_REGION_MAPPING
#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/ECLRegionMapping.hpp>
#include <cstddef>
#include <exception>
#include <initializer_list>
#include <numeric>
#include <stdexcept>
namespace {
std::vector<int> pvtnum(const std::size_t n = 10)
{
return std::vector<int>(n, 1);
}
std::vector<int> satnum()
{
return std::vector<int> {
1, 1, 1, 2, 2,
3, 3, 3, 2, 2,
};
}
std::vector<int> linear(const std::vector<int>::size_type n)
{
auto i = std::vector<int>(n);
std::iota(std::begin(i), std::end(i), 0);
return i;
}
template <class Coll1, class Coll2>
void equal_collection(const Coll1& c1, const Coll2& c2)
{
BOOST_CHECK_EQUAL_COLLECTIONS(std::begin(c1), std::end(c1),
std::begin(c2), std::end(c2));
}
}
BOOST_AUTO_TEST_SUITE (Full_Region_Mapping)
BOOST_AUTO_TEST_CASE (Constructor_Failure)
{
using RM = ::Opm::ECLRegionMapping;
BOOST_CHECK_THROW(RM{ std::vector<int>{} },
std::invalid_argument);
}
BOOST_AUTO_TEST_CASE (Single_Region)
{
const auto rm = ::Opm::ECLRegionMapping{ pvtnum(5) };
// All cells in single region => active regions == single ID.
{
const auto expect_actreg = std::vector<int>{1};
equal_collection(rm.activeRegions(), expect_actreg);
}
// Defaulted index subset => Index vector [0 .. reg.size()-1]
{
const auto expect_ix = std::vector<int>{ 0, 1, 2, 3, 4, };
equal_collection(rm.regionSubset(), expect_ix);
}
// All cells in single region => region's subset of index vector is
// [0 .. regionSubset().size()-1]
{
const auto expect_regix = std::vector<int>{ 0, 1, 2, 3, 4, };
equal_collection(rm.getRegionIndices(1), expect_regix);
}
// Invalid region ID (outside configured subset) => logic_error.
BOOST_CHECK_THROW(rm.getRegionIndices(1729),
std::logic_error);
}
BOOST_AUTO_TEST_CASE (Multiple_Regions)
{
const auto rm = ::Opm::ECLRegionMapping{ satnum() };
// Active regions returned in sorted order
{
const auto expect_actreg = std::vector<int>{1, 2, 3};
equal_collection(rm.activeRegions(), expect_actreg);
}
// Defaulted index subset => Index vector [0 .. reg.size()-1]
{
const auto expect_ix = std::vector<int>{
0, 1, 2, 3, 4,
5, 6, 7, 8, 9,
};
equal_collection(rm.regionSubset(), expect_ix);
}
// Cells in multiple regions => Must verify correct subset mappings.
{
const auto expect_regix_1 = std::vector<int>{ 0, 1, 2, };
const auto expect_regix_2 = std::vector<int>{ 3, 4,
8, 9 };
const auto expect_regix_3 = std::vector<int>{ 5, 6, 7, };
equal_collection(rm.getRegionIndices(1), expect_regix_1);
equal_collection(rm.getRegionIndices(2), expect_regix_2);
equal_collection(rm.getRegionIndices(3), expect_regix_3);
}
// Invalid region ID (outside configured subset) => logic_error.
BOOST_CHECK_THROW(rm.getRegionIndices(1701),
std::logic_error);
}
BOOST_AUTO_TEST_SUITE_END ()
// =====================================================================
BOOST_AUTO_TEST_SUITE (Subset_Region_Mapping)
BOOST_AUTO_TEST_CASE (Single_Region_Subset)
{
const auto rm = ::Opm::ECLRegionMapping{
pvtnum(5), std::vector<int>{ 1, 3, 4 }
};
// All cells in single region => active regions == single ID.
{
const auto expect_actreg = std::vector<int>{1};
equal_collection(rm.activeRegions(), expect_actreg);
}
// Explicit index subset => Index vector equal to this subset.
{
const auto expect_ix = std::vector<int>{ 1, 3, 4, };
equal_collection(rm.regionSubset(), expect_ix);
}
// All cells in single region => region's subset of index vector is
// [0 .. regionSubset().size()-1]
{
const auto expect_regix = std::vector<int>{ 0, 1, 2, };
equal_collection(rm.getRegionIndices(1), expect_regix);
}
}
BOOST_AUTO_TEST_CASE (Single_Region_MultiSampledSubset)
{
const auto cellIDs =
std::vector<int>{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
const auto rm = ::Opm::ECLRegionMapping{
pvtnum(5), cellIDs
};
// All cells in single region => active regions == single ID.
{
const auto expect_actreg = std::vector<int>{1};
equal_collection(rm.activeRegions(), expect_actreg);
}
// Explicit index subset => Index vector equal to this subset.
{
equal_collection(rm.regionSubset(), cellIDs);
}
// All cells in single region => region's subset of index vector is
// [0 .. regionSubset().size()-1]
{
const auto expect_regix = linear(cellIDs.size());
equal_collection(rm.getRegionIndices(1), expect_regix);
}
}
BOOST_AUTO_TEST_CASE (Multi_Region_Subset)
{
const auto cellIDs = std::vector<int> {
0, 1, /* 2, */ /* 3, */ 4,
5, 6, /* 7, */ /* 8, */ 9,
};
const auto rm = ::Opm::ECLRegionMapping{
satnum(), cellIDs
};
// Active regions returned in sorted order (cell subset covers all
// regions).
{
const auto expect_actreg = std::vector<int>{1, 2, 3};
equal_collection(rm.activeRegions(), expect_actreg);
}
// Explicit index subset => Index vector must match this subset.
{
equal_collection(rm.regionSubset(), cellIDs);
}
// Cells in multiple regions => Must verify correct subset mappings.
{
const auto expect_regix_1 = std::vector<int>{ 0, 1, };
const auto expect_regix_2 = std::vector<int>{ 2,
5, };
const auto expect_regix_3 = std::vector<int>{ 3, 4, };
equal_collection(rm.getRegionIndices(1), expect_regix_1);
equal_collection(rm.getRegionIndices(2), expect_regix_2);
equal_collection(rm.getRegionIndices(3), expect_regix_3);
}
}
BOOST_AUTO_TEST_CASE (Multi_Region_MultiSampledSubset)
{
const auto cellIDs = std::vector<int> {
0, 0, 0, 0, 0, 0, // 0 .. 5
9, 9, 8, 8, 3, 4, // 6 .. 11
5, 5, 2, 2, 7, 7, // 12 .. 17
0, 0, 0, 0, 0, 0, // 18 .. 23
};
const auto rm = ::Opm::ECLRegionMapping{
satnum(), cellIDs
};
// Active regions returned in sorted order (cell subset covers all
// regions).
{
const auto expect_actreg = std::vector<int>{1, 2, 3};
equal_collection(rm.activeRegions(), expect_actreg);
}
// Explicit index subset => Index vector must match this subset.
{
equal_collection(rm.regionSubset(), cellIDs);
}
// Cells in multiple regions => Must verify correct subset mappings.
{
// Note: Index subsets appear in sorted order by construction.
const auto expect_regix_1 = std::vector<int>{
0, 1, 2, 3, 4, 5, // 0 .. 5
// 6 .. 11
14, 15, // 12 .. 17
18, 19, 20, 21, 22, 23, // 18 .. 23
};
const auto expect_regix_2 = std::vector<int>{
// 0 .. 5
6, 7, 8, 9, 10, 11, // 6 .. 11
// 12 .. 17
// 18 .. 23
};
const auto expect_regix_3 = std::vector<int>{
// 0 .. 5
// 6 .. 11
12, 13, /* 14, 15 */ 16, 17 // 12 .. 17
// 18 .. 23
};
equal_collection(rm.getRegionIndices(1), expect_regix_1);
equal_collection(rm.getRegionIndices(2), expect_regix_2);
equal_collection(rm.getRegionIndices(3), expect_regix_3);
}
}
BOOST_AUTO_TEST_SUITE_END ()

View File

@ -28,7 +28,7 @@
#define NVERBOSE
#define BOOST_TEST_MODULE TEST_ASSEMBLED_CONNECTIONS
#define BOOST_TEST_MODULE TEST_UNIT_HANDLING
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <boost/test/unit_test.hpp>
@ -56,6 +56,22 @@ BOOST_AUTO_TEST_CASE (Metric)
{
auto M = ::Opm::ECLUnits::createUnitSystem(1);
// Density (kilogram/cubic(metres))
{
const auto scale = M->density();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Depth (metres)
{
const auto scale = M->depth();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Pressure (bars)
{
const auto scale = M->pressure();
@ -80,6 +96,22 @@ BOOST_AUTO_TEST_CASE (Metric)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Gas (sm3)
{
const auto scale = M->surfaceVolumeGas();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Liquid (sm3)
{
const auto scale = M->surfaceVolumeLiquid();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (day)
{
const auto scale = M->time();
@ -95,12 +127,52 @@ BOOST_AUTO_TEST_CASE (Metric)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Viscosity (cP)
{
const auto scale = M->viscosity();
const auto expect = 1.0e-3;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Dissolved Gas-Oil Ratio (Sm^3/Sm^3)
{
const auto scale = M->dissolvedGasOilRat();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Vaporised Oil-Gas Ratio (Sm^3/Sm^3)
{
const auto scale = M->vaporisedOilGasRat();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (Field)
{
auto F = ::Opm::ECLUnits::createUnitSystem(2);
// Density (pound/cubic(feet))
{
const auto scale = F->density();
const auto expect = 1.601846337396014e+01;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Depth (feet)
{
const auto scale = F->depth();
const auto expect = 0.3048; // 12 * 2.54
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Pressure (psi)
{
const auto scale = F->pressure();
@ -125,6 +197,22 @@ BOOST_AUTO_TEST_CASE (Field)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Gas (Mscf)
{
const auto scale = F->surfaceVolumeGas();
const auto expect = 2.831684659200000e+01;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Liquid (stb)
{
const auto scale = F->surfaceVolumeLiquid();
const auto expect = 1.589872949280001e-01;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (day)
{
const auto scale = F->time();
@ -140,12 +228,52 @@ BOOST_AUTO_TEST_CASE (Field)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Viscosity (cP)
{
const auto scale = F->viscosity();
const auto expect = 1.0e-3;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Dissolved Gas-Oil Ratio (Mscf/stb)
{
const auto scale = F->dissolvedGasOilRat();
const auto expect = 1.781076066790352e+02;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Vaporised Oil-Gas Ratio (stb/Mscf)
{
const auto scale = F->vaporisedOilGasRat();
const auto expect = 5.614583333333335e-03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (Lab)
{
auto L = ::Opm::ECLUnits::createUnitSystem(3);
// Density (gram/cubic(centi*meter))
{
const auto scale = L->density();
const auto expect = 1.0e3;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Depth (cm)
{
const auto scale = L->depth();
const auto expect = 0.01;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Pressure (atm)
{
const auto scale = L->pressure();
@ -170,6 +298,22 @@ BOOST_AUTO_TEST_CASE (Lab)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Gas (s(cm)^3)
{
const auto scale = L->surfaceVolumeGas();
const auto expect = 1.0e-06;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Liquid (s(cm)^3)
{
const auto scale = L->surfaceVolumeLiquid();
const auto expect = 1.0e-06;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (hour)
{
const auto scale = L->time();
@ -185,12 +329,52 @@ BOOST_AUTO_TEST_CASE (Lab)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Viscosity (cP)
{
const auto scale = L->viscosity();
const auto expect = 1.0e-3;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Dissolved Gas-Oil Ratio (s(cm)^3/s(cm)^3)
{
const auto scale = L->dissolvedGasOilRat();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Vaporised Oil-Gas Ratio (s(cm)^3/s(cm)^3)
{
const auto scale = L->vaporisedOilGasRat();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (PVT_M)
{
auto P = ::Opm::ECLUnits::createUnitSystem(4);
// Density (kilogram/cubic(meter))
{
const auto scale = P->density();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Depth (metres)
{
const auto scale = P->depth();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Pressure (atm)
{
const auto scale = P->pressure();
@ -215,6 +399,22 @@ BOOST_AUTO_TEST_CASE (PVT_M)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Gas (sm^3)
{
const auto scale = P->surfaceVolumeGas();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Surface Volume, Liquid (sm^3)
{
const auto scale = P->surfaceVolumeLiquid();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (day)
{
const auto scale = P->time();
@ -230,6 +430,30 @@ BOOST_AUTO_TEST_CASE (PVT_M)
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Viscosity (cP)
{
const auto scale = P->viscosity();
const auto expect = 1.0e-3;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Dissolved Gas-Oil Ratio (sm^3/sm^3)
{
const auto scale = P->dissolvedGasOilRat();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Vaporised Oil-Gas Ratio (sm^3/sm^3)
{
const auto scale = P->vaporisedOilGasRat();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_SUITE_END ()

View File

@ -14,46 +14,43 @@
# #
###########################################################################
# Mandatory call to project
project(opm-flowdiagnostics CXX)
cmake_minimum_required (VERSION 2.8)
# additional search modules
set( OPM_COMMON_ROOT "" CACHE PATH "Root directory containing OPM related cmake modules")
option(SIBLING_SEARCH "Search for other modules in sibling directories?" ON)
if(NOT OPM_COMMON_ROOT)
find_package(opm-common QUIET)
if(SIBLING_SEARCH AND NOT opm-common_DIR)
# guess the sibling dir
get_filename_component(_leaf_dir_name ${PROJECT_BINARY_DIR} NAME)
get_filename_component(_parent_full_dir ${PROJECT_BINARY_DIR} DIRECTORY)
get_filename_component(_parent_dir_name ${_parent_full_dir} NAME)
#Try if <module-name>/<build-dir> is used
get_filename_component(_modules_dir ${_parent_full_dir} DIRECTORY)
if(IS_DIRECTORY ${_modules_dir}/opm-common/${_leaf_dir_name})
set(opm-common_DIR ${_modules_dir}/opm-common/${_leaf_dir_name})
else()
string(REPLACE ${PROJECT_NAME} opm-common _opm_common_leaf ${_leaf_dir_name})
if(NOT _leaf_dir_name STREQUAL _opm_common_leaf
AND IS_DIRECTORY ${_parent_full_dir}/${_opm_common_leaf})
# We are using build directories named <prefix><module-name><postfix>
set(opm-common_DIR ${_parent_full_dir}/${_opm_common_leaf})
elseif(IS_DIRECTORY ${_parent_full_dir}/opm-common)
# All modules are in a common build dir
set(opm-common_DIR "${_parent_full_dir}/opm-common}")
endif()
endif()
endif()
if(opm-common_DIR AND NOT IS_DIRECTORY ${opm-common_DIR})
message(WARNING "Value ${opm-common_DIR} passed to variable"
" opm-common_DIR is not a directory")
endif()
if (opm-common_FOUND)
include(OpmInit)
else()
unset(opm-common_FOUND)
find_package(opm-common REQUIRED)
if (NOT OPM_COMMON_ROOT AND SIBLING_SEARCH)
set(OPM_COMMON_ROOT ${PROJECT_SOURCE_DIR}/../opm-common)
endif()
if (OPM_COMMON_ROOT)
list( APPEND CMAKE_MODULE_PATH "${OPM_COMMON_ROOT}/cmake/Modules")
include (OpmInit OPTIONAL RESULT_VARIABLE OPM_INIT)
set( OPM_MACROS_ROOT ${OPM_COMMON_ROOT} )
endif()
if (NOT OPM_INIT)
message( "" )
message( " /---------------------------------------------------------------------------------\\")
message( " | Could not locate the opm build macros. The opm build macros |")
message( " | are in a separate repository - instructions to proceed: |")
message( " | |")
message( " | 1. Clone the repository: git clone git@github.com:OPM/opm-common.git |")
message( " | |")
message( " | 2. Run cmake in the current project with -DOPM_COMMON_ROOT=<path>/opm-common |")
message( " | |")
message( " \\---------------------------------------------------------------------------------/")
message( "" )
message( FATAL_ERROR "Could not find OPM Macros")
endif()
endif()
include(OpmInit)
# not the same location as most of the other projects; this hook overrides
macro (dir_hook)

View File

@ -226,23 +226,19 @@ namespace FlowDiagnostics
// Helper for injectorProducerPairFlux().
double pairFlux(const CellSetValues& tracer,
const CellSet& well_cells,
const CellSetValues& inflow_flux,
const bool require_inflow)
{
double flux = 0.0;
for (const int cell : well_cells) {
for (const auto inflow : inflow_flux) {
const int cell = inflow.first;
const auto tracer_iter = tracer.find(cell);
if (tracer_iter != tracer.end()) {
// Tracer present in cell.
const auto source_iter = inflow_flux.find(cell);
if (source_iter != inflow_flux.end()) {
// Cell has source term.
const double source = source_iter->second;
if ((source > 0.0) == require_inflow) {
// Source term has correct sign.
flux += source * tracer_iter->second;
}
const double source = inflow.second;
if ((source > 0.0) == require_inflow) {
// Source term has correct sign.
flux += source * tracer_iter->second;
}
}
}
@ -268,14 +264,22 @@ namespace FlowDiagnostics
std::pair<double, double>
injectorProducerPairFlux(const Toolbox::Forward& injector_solution,
const Toolbox::Reverse& producer_solution,
const CellSet& injector_cells,
const CellSet& producer_cells,
const CellSetValues& inflow_flux)
const CellSetID& injector,
const CellSetID& producer,
const std::map<CellSetID, CellSetValues>& inflow_flux)
{
const auto& inj_tracer = injector_solution.fd.concentration(injector_cells.id());
const auto& prod_tracer = producer_solution.fd.concentration(producer_cells.id());
const double inj_flux = pairFlux(prod_tracer, injector_cells, inflow_flux, true);
const double prod_flux = pairFlux(inj_tracer, producer_cells, inflow_flux, false);
const auto& inj_tracer = injector_solution.fd.concentration(injector);
const auto& prod_tracer = producer_solution.fd.concentration(producer);
const auto inj_set_iter = inflow_flux.find(injector);
if (inj_set_iter == inflow_flux.end()) {
throw std::runtime_error("injectorProducerPairFlux(): Could not find requeste injector set in inflow fluxes.");
}
const auto prod_set_iter = inflow_flux.find(producer);
if (prod_set_iter == inflow_flux.end()) {
throw std::runtime_error("injectorProducerPairFlux(): Could not find requested producer set in inflow fluxes.");
}
const double inj_flux = pairFlux(prod_tracer, inj_set_iter->second, true);
const double prod_flux = pairFlux(inj_tracer, prod_set_iter->second, false);
return { inj_flux, prod_flux };
}

View File

@ -110,9 +110,9 @@ namespace FlowDiagnostics
std::pair<double, double>
injectorProducerPairFlux(const Toolbox::Forward& injector_solution,
const Toolbox::Reverse& producer_solution,
const CellSet& injector_cells,
const CellSet& producer_cells,
const CellSetValues& inflow_flux);
const CellSetID& injector,
const CellSetID& producer,
const std::map<CellSetID, CellSetValues>& inflow_flux);
} // namespace FlowDiagnostics

View File

@ -56,7 +56,7 @@ public:
void assignPoreVolume(const std::vector<double>& pvol);
void assignConnectionFlux(const ConnectionValues& flux);
void assignInflowFlux(const CellSetValues& inflow_flux);
void assignInflowFlux(const std::map<CellSetID, CellSetValues>& inflow_flux);
Forward injDiag (const std::vector<CellSet>& start_sets);
Reverse prodDiag(const std::vector<CellSet>& start_sets);
@ -66,6 +66,8 @@ private:
std::vector<double> pvol_;
ConnectionValues flux_;
std::map<CellSetID, CellSetValues> inj_flux_by_id_;
std::map<CellSetID, CellSetValues> prod_flux_by_id_;
CellSetValues only_inflow_flux_;
CellSetValues only_outflow_flux_;
@ -109,15 +111,20 @@ Toolbox::Impl::assignConnectionFlux(const ConnectionValues& flux)
}
void
Toolbox::Impl::assignInflowFlux(const CellSetValues& inflow_flux)
Toolbox::Impl::assignInflowFlux(const std::map<CellSetID, CellSetValues>& inflow_flux)
{
only_inflow_flux_.clear();
only_outflow_flux_.clear();
for (const auto& data : inflow_flux) {
if (data.second > 0.0) {
only_inflow_flux_[data.first] = data.second;
} else if (data.second < 0.0) {
only_outflow_flux_[data.first] = -data.second;
for (const auto& inflow_set : inflow_flux) {
const CellSetID& id = inflow_set.first;
for (const auto& data : inflow_set.second) {
if (data.second > 0.0) {
only_inflow_flux_[data.first] += data.second;
inj_flux_by_id_[id].insert(data);
} else if (data.second < 0.0) {
only_outflow_flux_[data.first] += -data.second;
prod_flux_by_id_[id].insert(std::make_pair(data.first, -data.second));
}
}
}
}
@ -132,6 +139,9 @@ Toolbox::Impl::injDiag(const std::vector<CellSet>& start_sets)
// Check that start sets are valid.
for (const auto& start : start_sets) {
if (inj_flux_by_id_.find(start.id()) == inj_flux_by_id_.end()) {
throw std::runtime_error("Start set ID not present in data passed to assignInflowFlux().");
}
for (const int cell : start) {
if (only_inflow_flux_.count(cell) != 1 || only_outflow_flux_.count(cell) != 0) {
throw std::runtime_error("Start set inconsistent with assignInflowFlux()-given values");
@ -151,7 +161,7 @@ Toolbox::Impl::injDiag(const std::vector<CellSet>& start_sets)
sol.assignGlobalToF(solver.solveGlobal());
for (const auto& start : start_sets) {
auto solution = solver.solveLocal(start);
auto solution = solver.solveLocal(inj_flux_by_id_[start.id()]);
sol.assign(start.id(), ToF{ solution.tof });
sol.assign(start.id(), Conc{ solution.concentration });
}
@ -169,6 +179,9 @@ Toolbox::Impl::prodDiag(const std::vector<CellSet>& start_sets)
// Check that start sets are valid.
for (const auto& start : start_sets) {
if (prod_flux_by_id_.find(start.id()) == prod_flux_by_id_.end()) {
throw std::runtime_error("Start set ID not present in data passed to assignInflowFlux().");
}
for (const int cell : start) {
if (only_inflow_flux_.count(cell) != 0 || only_outflow_flux_.count(cell) != 1) {
throw std::runtime_error("Start set inconsistent with assignInflowFlux()-given values");
@ -188,7 +201,7 @@ Toolbox::Impl::prodDiag(const std::vector<CellSet>& start_sets)
sol.assignGlobalToF(solver.solveGlobal());
for (const auto& start : start_sets) {
auto solution = solver.solveLocal(start);
auto solution = solver.solveLocal(prod_flux_by_id_[start.id()]);
sol.assign(start.id(), ToF{ solution.tof });
sol.assign(start.id(), Conc{ solution.concentration });
}
@ -271,7 +284,7 @@ Toolbox::assignConnectionFlux(const ConnectionValues& flux)
}
void
Toolbox::assignInflowFlux(const CellSetValues& inflow_flux)
Toolbox::assignInflowFlux(const std::map<CellSetID, CellSetValues>& inflow_flux)
{
pImpl_->assignInflowFlux(inflow_flux);
}

View File

@ -62,7 +62,9 @@ namespace FlowDiagnostics
/// Inflow fluxes (injection) should be positive, outflow
/// fluxes (production) should be negative, both should be
/// given in the inflow_flux argument passed to this method.
void assignInflowFlux(const CellSetValues& inflow_flux);
/// Values from a single well should typically be associated with
/// a single CellSetID and be a single CellSetValues object.
void assignInflowFlux(const std::map<CellSetID, CellSetValues>& inflow_flux);
struct Forward
{

View File

@ -100,6 +100,7 @@ namespace FlowDiagnostics
, influx_(std::move(inout.influx))
, outflux_(std::move(inout.outflux))
, source_term_(expandSparse(pore_volumes.size(), source_inflow))
, local_source_term_(pore_volumes.size(), 0.0)
{
}
@ -112,10 +113,12 @@ namespace FlowDiagnostics
// Reset solver variables and set source terms.
prepareForSolve();
setupStartArrayFromSource();
local_source_term_ = source_term_;
// Compute topological ordering and solve.
computeOrdering();
solve();
std::fill(local_source_term_.begin(), local_source_term_.end(), 0.0);
// Return computed time-of-flight.
return tof_;
@ -125,7 +128,7 @@ namespace FlowDiagnostics
TracerTofSolver::LocalSolution TracerTofSolver::solveLocal(const CellSet& startset)
TracerTofSolver::LocalSolution TracerTofSolver::solveLocal(const CellSetValues& startset)
{
// Reset solver variables and set source terms.
prepareForSolve();
@ -133,7 +136,9 @@ namespace FlowDiagnostics
// Compute local topological ordering and solve.
computeLocalOrdering(startset);
setupLocalSource(startset);
solve();
cleanupLocalSource(startset);
// Return computed time-of-flight.
CellSetValues local_tof;
@ -143,6 +148,10 @@ namespace FlowDiagnostics
const int cell = sequence_[element];
local_tof[cell] = tof_[cell];
local_tracer[cell] = tracer_[cell];
// Verify that tracer values are greater than zero
if (tracer_[cell] <= 0.0) {
throw std::logic_error("Tracer is zero in non-isolated cell.");
}
}
return LocalSolution{ std::move(local_tof), std::move(local_tracer) };
}
@ -170,9 +179,10 @@ namespace FlowDiagnostics
void TracerTofSolver::setupStartArray(const CellSet& startset)
void TracerTofSolver::setupStartArray(const CellSetValues& startset)
{
for (const int cell : startset) {
for (const auto& startpoint : startset) {
const int cell = startpoint.first;
is_start_[cell] = 1;
}
}
@ -227,10 +237,14 @@ namespace FlowDiagnostics
void TracerTofSolver::computeLocalOrdering(const CellSet& startset)
void TracerTofSolver::computeLocalOrdering(const CellSetValues& startset)
{
// Extract start cells.
std::vector<int> startcells(startset.begin(), startset.end());
std::vector<int> startcells;
startcells.reserve(startset.size());
for (const auto& startpoint : startset) {
startcells.push_back(startpoint.first);
}
// Compute reverse topological ordering.
const size_t num_cells = pv_.size();
@ -251,7 +265,7 @@ namespace FlowDiagnostics
}
// Extract data from solution.
sequence_.resize(num_cells); // For local solutions this is the upper limit of the size. TODO: use exact size.
sequence_.resize(num_cells); // For local solutions this is the upper limit of the size. Will give proper size afterwards.
const int num_comp = tarjan_get_numcomponents(result.get());
component_starts_.resize(num_comp + 1);
component_starts_[0] = 0;
@ -260,6 +274,32 @@ namespace FlowDiagnostics
std::copy(tc.vertex, tc.vertex + tc.size, sequence_.begin() + component_starts_[comp]);
component_starts_[comp + 1] = component_starts_[comp] + tc.size;
}
sequence_.resize(component_starts_.back());
}
void TracerTofSolver::setupLocalSource(const CellSetValues& startset)
{
for (const auto& startpoint : startset) {
if (startpoint.second < 0.0) {
throw std::logic_error("Start set for local solve has negative source value.");
}
local_source_term_[startpoint.first] = startpoint.second;
}
}
void TracerTofSolver::cleanupLocalSource(const CellSetValues& startset)
{
for (const auto& startpoint : startset) {
local_source_term_[startpoint.first] = 0.0;
}
}
@ -292,25 +332,12 @@ namespace FlowDiagnostics
void TracerTofSolver::solveSingleCell(const int cell)
{
// Compute influx (divisor of tof expression).
double source = source_term_[cell]; // Initial tof for well cell equal to fill time.
double source = source_term_[cell];
if (source == 0.0 && is_start_[cell]) {
source = std::numeric_limits<double>::infinity(); // Gives 0 tof in start cell.
}
const double total_influx = influx_[cell] + source;
// Cap time-of-flight if time to fill cell is greater than
// max_tof_. Note that cells may still have larger than
// max_tof_ after solveSingleCell() when including upwind
// contributions, and those in turn can affect cells
// downstream (so capping in this method will not produce the
// same result). All tofs will finally be capped in solve() as
// a post-process. The reason for the somewhat convoluted
// behaviour is to match existing MRST results.
if (total_influx < pv_[cell] / max_tof_) {
tof_[cell] = max_tof_;
return;
}
// Compute upwind contribution.
double upwind_tof_contrib = 0.0;
double upwind_tracer_contrib = 0.0;
@ -325,21 +352,41 @@ namespace FlowDiagnostics
// should get a contribution from the local source term
// (which is then considered to be containing the
// currently considered tracer).
//
// Start cells should therefore never have a zero source
// term. This may need to change in the future to support
// local tracing from arbitrary locations.
upwind_tracer_contrib += source;
upwind_tracer_contrib += local_source_term_[cell];
}
// Compute time-of-flight and tracer.
tracer_[cell] = upwind_tracer_contrib / total_influx;
// The following should be true if Tarjan was done correctly.
// Note that global Tarjan will also visit isolated cells,
// so it is possible to have exactly zero.
assert(total_influx >= 0.0);
assert(upwind_tracer_contrib >= 0.0);
// Compute tracer.
if (total_influx == 0.0) {
assert(upwind_tracer_contrib == 0.0);
// Isolated cell.
tracer_[cell] = 0.0;
} else {
tracer_[cell] = upwind_tracer_contrib / total_influx;
}
// Compute time-of-flight.
if (tracer_[cell] > 0.0) {
tof_[cell] = (pv_[cell]*tracer_[cell] + upwind_tof_contrib)
/ (total_influx * tracer_[cell]);
} else {
tof_[cell] = max_tof_;
}
else {
// Cap time-of-flight if time to fill cell is greater than
// max_tof_. Note that cells may still have larger than
// max_tof_ after solveSingleCell() when including upwind
// contributions, and those in turn can affect cells
// downstream (so capping in this method will not produce the
// same result). All tofs will finally be capped in solve() as
// a post-process. The reason for the somewhat convoluted
// behaviour is to match existing MRST results.
if (total_influx < pv_[cell] / max_tof_) {
tof_[cell] = max_tof_;
}
}
@ -354,17 +401,23 @@ namespace FlowDiagnostics
++num_multicell_;
max_size_multicell_ = std::max(max_size_multicell_, num_cells);
// Using a Gauss-Seidel approach.
double max_delta = 1e100;
// Using a simple Gauss-Seidel approach.
double max_tof_delta = 1e100;
double max_tracer_delta = 1e100;
int num_iter = 0;
while (max_delta > gauss_seidel_tol_) {
max_delta = 0.0;
while (max_tof_delta > gauss_seidel_tof_tol_ || max_tracer_delta > gauss_seidel_tracer_tol_) {
max_tof_delta = 0.0;
max_tracer_delta = 0.0;
++num_iter;
for (int ci = 0; ci < num_cells; ++ci) {
const int cell = cells[ci];
const double tof_before = tof_[cell];
const double tracer_before = tracer_[cell];
solveSingleCell(cell);
max_delta = std::max(max_delta, std::fabs(tof_[cell] - tof_before));
max_tof_delta = std::max(max_tof_delta, std::fabs(tof_[cell] - tof_before));
const bool zero_change = ((tracer_before == 0.0) != (tracer_[cell] == 0.0));
const double tracer_change = zero_change ? 1.0 : std::fabs(tracer_[cell] - tracer_before);
max_tracer_delta = std::max(max_tracer_delta, tracer_change);
}
}
max_iter_multicell_ = std::max(max_iter_multicell_, num_iter);

View File

@ -48,9 +48,11 @@ namespace FlowDiagnostics
{
public:
/// Initialize solver with a given flow graph (a weighted,
/// directed asyclic graph) containing the out-fluxes from
/// directed acyclic graph) containing the out-fluxes from
/// each cell, the reverse graph (with in-fluxes from each
/// cell), pore volumes and inflow sources (positive).
/// cell), pore volumes and all (positive) inflow sources. If
/// there are multiple inflow sources for a single cell, they
/// should be added before passing to this function.
TracerTofSolver(const AssembledConnections& graph,
const AssembledConnections& reverse_graph,
const std::vector<double>& pore_volumes,
@ -69,10 +71,11 @@ namespace FlowDiagnostics
/// Compute a local solution tracer and time-of-flight solution.
///
/// Local means that only cells downwind from he startset are considered.
/// The solution is therefore potentially sparse.
/// TODO: not implemented!
LocalSolution solveLocal(const CellSet& startset);
/// Local means that only cells downwind from he startset are
/// considered. The solution is therefore potentially sparse.
/// The startset must contain the (nonnegative) source term for
/// each start cell.
LocalSolution solveLocal(const CellSetValues& startset);
private:
@ -84,6 +87,7 @@ namespace FlowDiagnostics
const std::vector<double> influx_;
const std::vector<double> outflux_;
std::vector<double> source_term_;
std::vector<double> local_source_term_;
std::vector<char> is_start_; // char to avoid the nasty vector<bool> specialization
std::vector<int> sequence_;
std::vector<int> component_starts_;
@ -92,8 +96,9 @@ namespace FlowDiagnostics
int num_multicell_ = 0;
int max_size_multicell_ = 0;
int max_iter_multicell_ = 0;
const double gauss_seidel_tol_ = 1e-3;
const double max_tof_ = 200.0 * 365.0 * 24.0 * 60.0 * 60.0; // 200 years.
const double gauss_seidel_tof_tol_ = max_tof_ / 1e12;
const double gauss_seidel_tracer_tol_ = 1e-9;
// -------------- Private helper class --------------
@ -109,13 +114,17 @@ namespace FlowDiagnostics
void prepareForSolve();
void setupStartArray(const CellSet& startset);
void setupStartArray(const CellSetValues& startset);
void setupStartArrayFromSource();
void computeOrdering();
void computeLocalOrdering(const CellSet& startset);
void computeLocalOrdering(const CellSetValues& startset);
void setupLocalSource(const CellSetValues& startset);
void cleanupLocalSource(const CellSetValues& startset);
void solve();

View File

@ -222,7 +222,7 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
const auto& flux = cas.flux();
// Create well in/out flows.
CellSetValues wellflow = { {0, 0.3}, {4, -0.3} };
std::map<CellSetID, CellSetValues> wellflow = { { CellSetID("I-1"), {{0, 0.3}} }, { CellSetID("P-1"), {{4, -0.3}} } };
Toolbox diagTool(graph);
diagTool.assignPoreVolume(pv);
@ -291,7 +291,7 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
const double vol12 = injectorProducerPairVolume(fwd, rev, pv, CellSetID("I-1"), CellSetID("P-1"));
BOOST_CHECK_CLOSE(vol12, expectedVol12, 1e-10);
const auto pairflux = injectorProducerPairFlux(fwd, rev, inje[0], prod[0], wellflow);
const auto pairflux = injectorProducerPairFlux(fwd, rev, CellSetID("I-1"), CellSetID("P-1"), wellflow);
BOOST_CHECK_CLOSE(pairflux.first, 0.3, 1e-10);
BOOST_CHECK_CLOSE(pairflux.second, -0.3, 1e-10);
}

View File

@ -216,7 +216,7 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
}
// Create well in/out flows.
CellSetValues wellflow = { {0, 0.3}, {4, -0.3} };
std::map<CellSetID, CellSetValues> wellflow = { { CellSetID("I-1"), {{0, 0.3}} }, { CellSetID("P-1"), {{4, -0.3}} } };
Toolbox diagTool(graph);
diagTool.assignPoreVolume(cas.poreVolume());
@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
const int first_cell = 0;
const int last_cell = cas.connectivity().numCells() - 1;
auto start = std::vector<CellSet>{ CellSet(CellSetID("I-1"), {first_cell}),
CellSet(CellSetID("I-2"), {last_cell}) };
CellSet(CellSetID("P-1"), {last_cell}) };
BOOST_CHECK_THROW(diagTool.computeInjectionDiagnostics(start), std::runtime_error);
BOOST_CHECK_THROW(diagTool.computeProductionDiagnostics(start), std::runtime_error);
}
@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
const int first_cell = 0;
const int last_cell = cas.connectivity().numCells() - 1;
auto start_fwd = std::vector<CellSet>{ CellSet(CellSetID("I-1"), {first_cell}) };
auto start_rev = std::vector<CellSet>{ CellSet(CellSetID("I-2"), {last_cell}) };
auto start_rev = std::vector<CellSet>{ CellSet(CellSetID("P-1"), {last_cell}) };
const auto fwd = diagTool.computeInjectionDiagnostics(start_fwd);
const auto rev = diagTool.computeProductionDiagnostics(start_rev);
@ -426,7 +426,9 @@ BOOST_AUTO_TEST_CASE (LocalSolutions)
flux(C{6}, P{0}) = -0.6;
// Create well in/out flows.
CellSetValues wellflow = { {0, 0.3}, {3, 0.3}, {2, -0.6} };
std::map<CellSetID, CellSetValues> wellflow = { { CellSetID("I-1"), {{0, 0.3}} },
{ CellSetID("I-2"), {{3, 0.3}} },
{ CellSetID("P-1"), {{2, -0.6}} } };
Toolbox diagTool(graph);
diagTool.assignPoreVolume(cas.poreVolume());
@ -624,7 +626,9 @@ BOOST_AUTO_TEST_CASE (LocalSolutionsWithMidflowSource)
flux(C{1}, P{0}) = 0.6;
// Create well in/out flows.
CellSetValues wellflow = { {0, 0.3}, {1, 0.3}, {2, -0.6} };
std::map<CellSetID, CellSetValues> wellflow = { { CellSetID("I-1"), {{0, 0.3}} },
{ CellSetID("I-2"), {{1, 0.3}} },
{ CellSetID("P-1"), {{2, -0.6}} } };
Toolbox diagTool(graph);
diagTool.assignPoreVolume(cas.poreVolume());
@ -744,4 +748,206 @@ BOOST_AUTO_TEST_CASE (LocalSolutionsWithMidflowSource)
// Arrows indicate a flux of 0.3, O is a source of 0.3
// and X is a sink of 0.3 (each cell has a pore volume of 0.3).
// -----------------------------
// | | |
// | O O -> XX |
// | "I-1" "I-2" -> "P-1" |
// | | |
// -----------------------------
// Cell indices:
// -----------------------------
// | | |
// | | |
// | 0 | 1 |
// | | |
// -----------------------------
// Expected global injection TOF:
// -----------------------------
// | | |
// | | |
// | 0.5 | 1.0 |
// | | |
// -----------------------------
// Expected global production TOF:
// -----------------------------
// | | |
// | | |
// | 1.0 | 0.5 |
// | | |
// -----------------------------
// Expected local tracer I-1:
// -----------------------------
// | | |
// | | |
// | 0.5 | 0.5 |
// | | |
// -----------------------------
// Expected local tracer I-2:
// -----------------------------
// | | |
// | | |
// | 0.5 | 0.5 |
// | | |
// -----------------------------
// Expected local tof I-1:
// -----------------------------
// | | |
// | | |
// | 0.5 | 1.0 |
// | | |
// -----------------------------
// Expected local tof I-2:
// -----------------------------
// | | |
// | | |
// | 0.5 | 1.0 |
// | | |
// -----------------------------
BOOST_AUTO_TEST_CASE (LocalSolutionsPerfSameCell)
{
BOOST_TEST_MESSAGE("============== Test: LocalSolutionsPerfSameCell ==============");
using namespace Opm::FlowDiagnostics;
const auto cas = Setup(2, 1);
const auto& graph = cas.connectivity();
// Create fluxes.
ConnectionValues flux(ConnectionValues::NumConnections{ graph.numConnections() },
ConnectionValues::NumPhases { 1 });
const size_t nconn = cas.connectivity().numConnections();
for (size_t conn = 0; conn < nconn; ++conn) {
BOOST_TEST_MESSAGE("Connection " << conn << " connects cells "
<< graph.connection(conn).first << " and "
<< graph.connection(conn).second);
}
using C = ConnectionValues::ConnID;
using P = ConnectionValues::PhaseID;
flux(C{0}, P{0}) = 0.6;
// Create well in/out flows.
std::map<CellSetID, CellSetValues> wellflow = { { CellSetID("I-1"), {{0, 0.3}} },
{ CellSetID("I-2"), {{0, 0.3}} },
{ CellSetID("P-1"), {{1, -0.6}} } };
Toolbox diagTool(graph);
diagTool.assignPoreVolume(cas.poreVolume());
diagTool.assignConnectionFlux(flux);
diagTool.assignInflowFlux(wellflow);
auto injstart = std::vector<CellSet>{ CellSet(CellSetID("I-1"), {0}),
CellSet(CellSetID("I-2"), {0}) };
auto prdstart = std::vector<CellSet>{ CellSet(CellSetID("P-1"), {1}) };
const auto fwd = diagTool.computeInjectionDiagnostics(injstart);
const auto rev = diagTool.computeProductionDiagnostics(prdstart);
// Global ToF field (accumulated from all injectors)
{
BOOST_TEST_MESSAGE("== Global injector ToF");
const auto tof = fwd.fd.timeOfFlight();
BOOST_REQUIRE_EQUAL(tof.size(), cas.connectivity().numCells());
std::vector<double> expected = { 0.5, 1.0 };
check_is_close(tof, expected);
}
// Global ToF field (accumulated from all producers)
{
BOOST_TEST_MESSAGE("== Global producer ToF");
const auto tof = rev.fd.timeOfFlight();
BOOST_REQUIRE_EQUAL(tof.size(), cas.connectivity().numCells());
std::vector<double> expected = { 1.0, 0.5 };
check_is_close(tof, expected);
}
// Verify set of start points.
{
using VCS = std::vector<Opm::FlowDiagnostics::CellSet>;
using VCSI = std::vector<Opm::FlowDiagnostics::CellSetID>;
using P = std::pair<VCS, VCSI>;
std::vector<P> pairs { P{ injstart, fwd.fd.startPoints() }, P{ prdstart, rev.fd.startPoints() } };
for (const auto& p : pairs) {
const auto& s1 = p.first;
const auto& s2 = p.second;
BOOST_CHECK_EQUAL(s1.size(), s2.size());
for (const auto& pt : s2) {
// ID of 'pt' *MUST* be in set of identified start points.
auto pos = std::find_if(s1.begin(), s1.end(),
[&pt](const CellSet& s)
{
return s.id().to_string() == pt.to_string();
});
BOOST_CHECK(pos != s1.end());
}
}
}
// Local I-1 tracer concentration.
{
BOOST_TEST_MESSAGE("== I-1 tracer");
const auto conc = fwd.fd.concentration(CellSetID("I-1"));
std::vector<std::pair<int, double>> expected = { {0, 0.5}, {1, 0.5} };
BOOST_REQUIRE_EQUAL(conc.size(), expected.size());
int i = 0;
for (const auto& v : conc) {
BOOST_TEST_MESSAGE("Conc[" << v.first << "] = " << v.second);
BOOST_CHECK_EQUAL(v.first, expected[i].first);
BOOST_CHECK_CLOSE(v.second, expected[i].second, 1.0e-10);
++i;
}
}
// Local I-1 tof.
{
BOOST_TEST_MESSAGE("== I-1 tof");
const auto tof = fwd.fd.timeOfFlight(CellSetID("I-1"));
std::vector<std::pair<int, double>> expected = { {0, 0.5}, {1, 1.0} };
BOOST_REQUIRE_EQUAL(tof.size(), expected.size());
int i = 0;
for (const auto& v : tof) {
BOOST_TEST_MESSAGE("ToF[" << v.first << "] = " << v.second);
BOOST_CHECK_EQUAL(v.first, expected[i].first);
BOOST_CHECK_CLOSE(v.second, expected[i].second, 1.0e-10);
++i;
}
}
// Local I-2 tracer concentration.
{
BOOST_TEST_MESSAGE("== I-2 tracer");
const auto conc = fwd.fd.concentration(CellSetID("I-2"));
std::vector<std::pair<int, double>> expected = { {0, 0.5}, {1, 0.5} };
BOOST_REQUIRE_EQUAL(conc.size(), expected.size());
int i = 0;
for (const auto& v : conc) {
BOOST_TEST_MESSAGE("Conc[" << v.first << "] = " << v.second);
BOOST_CHECK_EQUAL(v.first, expected[i].first);
BOOST_CHECK_CLOSE(v.second, expected[i].second, 1.0e-10);
++i;
}
}
// Local I-2 tof.
{
BOOST_TEST_MESSAGE("== I-2 tof");
const auto tof = fwd.fd.timeOfFlight(CellSetID("I-2"));
std::vector<std::pair<int, double>> expected = { {0, 0.5}, {1, 1.0} };
BOOST_REQUIRE_EQUAL(tof.size(), expected.size());
int i = 0;
for (const auto& v : tof) {
BOOST_TEST_MESSAGE("ToF[" << v.first << "] = " << v.second);
BOOST_CHECK_EQUAL(v.first, expected[i].first);
BOOST_CHECK_CLOSE(v.second, expected[i].second, 1.0e-10);
++i;
}
}
}
BOOST_AUTO_TEST_SUITE_END()