#1206 Updated the opm-flowdiag libraries

This commit is contained in:
Jacob Støren
2017-02-09 14:14:02 +01:00
parent 65f36d6a3f
commit 8596c3f79c
18 changed files with 2945 additions and 310 deletions

View File

@@ -13,8 +13,10 @@ set(ERT_GITHUB_SHA "e2a5a9cc20705537d07822958d925e092a323367")
set(OPM_COMMON_GITHUB_SHA "1216bc052542f24ec6fcfbe1947d52e6300ff754") set(OPM_COMMON_GITHUB_SHA "1216bc052542f24ec6fcfbe1947d52e6300ff754")
set(OPM_PARSER_GITHUB_SHA "a3496df501a4369fd827fbf0ff893d03deff425f") set(OPM_PARSER_GITHUB_SHA "a3496df501a4369fd827fbf0ff893d03deff425f")
set(OPM_FLOWDIAGNOSTICS_SHA "80190c28ae0fd4a82cfe88c827559029982db83b") set(OPM_FLOWDIAGNOSTICS_SHA "02503abf0043bb08062e358e460a0c8231b6225c")
set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "01ecb8fc22cb70d883edaad398bffc49878859c3") set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "09057e5767e492fa44c5e9ae78e9e1fcc9694b6c")
# sha for Units.hpp 9a679071dd0066236154852c39a9e0b3c3ac4873
set(STRPRODUCTVER ${RESINSIGHT_MAJOR_VERSION}.${RESINSIGHT_MINOR_VERSION}.${RESINSIGHT_INCREMENT_VERSION}) set(STRPRODUCTVER ${RESINSIGHT_MAJOR_VERSION}.${RESINSIGHT_MINOR_VERSION}.${RESINSIGHT_INCREMENT_VERSION})

View File

@@ -0,0 +1,58 @@
# Set absolute tolerance to be used for testing
Set (abs_tol 5.0e-8)
Set (rel_tol 1.0e-13)
# Input:
# - casename: with or without extension
#
Macro (add_acceptance_test casename)
String (REGEX REPLACE "\\.[^.]*$" "" basename "${casename}")
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}")
EndMacro (add_acceptance_test)
# Input
# - casename: with or without extension
#
Macro (add_trans_acceptance_test casename)
String (REGEX REPLACE "\\.[^.]*$" "" basename "${casename}")
Add_Test (NAME Trans_accept_${casename}
COMMAND runTransTest
"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}")
EndMacro (add_trans_acceptance_test)
# Input
# - casename: with or without extension
# - strings identifying which physical quantities to compare
Macro (add_celldata_acceptance_test casename)
String (REGEX REPLACE "\\.[^.]*$" "" basename "${casename}")
Add_Test (NAME CellData_accept_${casename}
COMMAND runLinearisedCellDataTest
"case=${OPM_DATA_ROOT}/flow_diagnostic_test/eclipse-simulation/${basename}"
"ref-dir=${OPM_DATA_ROOT}/flow_diagnostic_test/fd-ref-data/${basename}"
"quant=${ARGN}" "atol=${abs_tol}" "rtol=${rel_tol}")
EndMacro (add_celldata_acceptance_test)
If (NOT TARGET test-suite)
Add_Custom_Target (test-suite)
EndIf ()
# Acceptance tests
Add_Acceptance_Test (SIMPLE_2PH_W_FAULT_LGR)
Add_Trans_Acceptance_Test (SIMPLE_2PH_W_FAULT_LGR)
Add_CellData_Acceptance_Test (SIMPLE_2PH_W_FAULT_LGR "pressure")

View File

@@ -59,6 +59,10 @@ endif()
macro (dir_hook) macro (dir_hook)
endmacro (dir_hook) endmacro (dir_hook)
# Look for the opm-data repository; if found the variable
# HAVE_OPM_DATA will be set to true.
include (Findopm-data)
# list of prerequisites for this particular project; this is in a # list of prerequisites for this particular project; this is in a
# separate file (in cmake/Modules sub-directory) because it is shared # separate file (in cmake/Modules sub-directory) because it is shared
# with the find module # with the find module
@@ -91,3 +95,7 @@ endmacro (install_hook)
# all setup common to the OPM library modules is done here # all setup common to the OPM library modules is done here
include (OpmLibMain) include (OpmLibMain)
if (HAVE_OPM_DATA)
include (${CMAKE_CURRENT_SOURCE_DIR}/AcceptanceTests.cmake)
endif()

View File

@@ -21,22 +21,30 @@
# the library needs it. # the library needs it.
list (APPEND MAIN_SOURCE_FILES list (APPEND MAIN_SOURCE_FILES
opm/utility/ECLFluxCalc.cpp
opm/utility/ECLGraph.cpp opm/utility/ECLGraph.cpp
opm/utility/ECLResultData.cpp opm/utility/ECLResultData.cpp
opm/utility/ECLUnitHandling.cpp
opm/utility/ECLWellSolution.cpp opm/utility/ECLWellSolution.cpp
) )
list (APPEND TEST_SOURCE_FILES list (APPEND TEST_SOURCE_FILES
tests/test_eclunithandling.cpp
) )
list (APPEND EXAMPLE_SOURCE_FILES list (APPEND EXAMPLE_SOURCE_FILES
examples/computeLocalSolutions.cpp examples/computeLocalSolutions.cpp
examples/computeToFandTracers.cpp examples/computeToFandTracers.cpp
examples/computeTracers.cpp examples/computeTracers.cpp
tests/runAcceptanceTest.cpp
tests/runLinearisedCellDataTest.cpp
tests/runTransTest.cpp
) )
list (APPEND PUBLIC_HEADER_FILES list (APPEND PUBLIC_HEADER_FILES
opm/utility/ECLFluxCalc.hpp
opm/utility/ECLGraph.hpp opm/utility/ECLGraph.hpp
opm/utility/ECLResultData.hpp opm/utility/ECLResultData.hpp
opm/utility/ECLUnitHandling.hpp
opm/utility/ECLWellSolution.hpp opm/utility/ECLWellSolution.hpp
) )

View File

@@ -29,6 +29,7 @@
#include <opm/flowdiagnostics/ConnectionValues.hpp> #include <opm/flowdiagnostics/ConnectionValues.hpp>
#include <opm/flowdiagnostics/Toolbox.hpp> #include <opm/flowdiagnostics/Toolbox.hpp>
#include <opm/utility/ECLFluxCalc.hpp>
#include <opm/utility/ECLGraph.hpp> #include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLWellSolution.hpp> #include <opm/utility/ECLWellSolution.hpp>
@@ -80,38 +81,31 @@ namespace example {
} }
inline Opm::FlowDiagnostics::ConnectionValues inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G) extractFluxField(const Opm::ECLGraph& G, const bool compute_fluxes)
{ {
using ConnVals = Opm::FlowDiagnostics::ConnectionValues; using ConnVals = Opm::FlowDiagnostics::ConnectionValues;
auto flux = ConnVals(ConnVals::NumConnections{G.numConnections()},
using NConn = ConnVals::NumConnections; ConnVals::NumPhases{3});
using NPhas = ConnVals::NumPhases;
const auto nconn = NConn{G.numConnections()};
const auto nphas = NPhas{3};
auto flux = ConnVals(nconn, nphas);
auto phas = ConnVals::PhaseID{0}; auto phas = ConnVals::PhaseID{0};
for (const auto& p : { Opm::ECLGraph::PhaseIndex::Aqua , Opm::ECLFluxCalc calc(G);
const auto phases = { Opm::ECLGraph::PhaseIndex::Aqua ,
Opm::ECLGraph::PhaseIndex::Liquid , Opm::ECLGraph::PhaseIndex::Liquid ,
Opm::ECLGraph::PhaseIndex::Vapour }) Opm::ECLGraph::PhaseIndex::Vapour };
for (const auto& p : phases)
{ {
const auto pflux = G.flux(p); const auto pflux = compute_fluxes ? calc.flux(p) : G.flux(p);
if (! pflux.empty()) { if (! pflux.empty()) {
assert (pflux.size() == nconn.total); assert (pflux.size() == flux.numConnections());
auto conn = ConnVals::ConnID{0}; auto conn = ConnVals::ConnID{0};
for (const auto& v : pflux) { for (const auto& v : pflux) {
flux(conn, phas) = v; flux(conn, phas) = v;
conn.id += 1; conn.id += 1;
} }
} }
phas.id += 1; phas.id += 1;
} }
@@ -138,72 +132,60 @@ namespace example {
return inflow; return inflow;
} }
namespace Hack {
inline Opm::FlowDiagnostics::ConnectionValues
convert_flux_to_SI(Opm::FlowDiagnostics::ConnectionValues&& fl)
struct FilePaths
{ {
using Co = Opm::FlowDiagnostics::ConnectionValues::ConnID; FilePaths(const Opm::parameter::ParameterGroup& param)
using Ph = Opm::FlowDiagnostics::ConnectionValues::PhaseID; {
const string casename = param.getDefault<string>("case", "DEFAULT_CASE_NAME");
const auto nconn = fl.numConnections(); grid = param.has("grid") ? param.get<string>("grid")
const auto nphas = fl.numPhases(); : deriveFileName(casename, { ".EGRID", ".FEGRID", ".GRID", ".FGRID" });
init = param.has("init") ? param.get<string>("init")
for (auto phas = Ph{0}; phas.id < nphas; ++phas.id) { : deriveFileName(casename, { ".INIT", ".FINIT" });
for (auto conn = Co{0}; conn.id < nconn; ++conn.id) { restart = param.has("restart") ? param.get<string>("restart")
fl(conn, phas) /= 86400; : deriveFileName(casename, { ".UNRST", ".FUNRST" });
}
} }
return fl; using path = boost::filesystem::path;
} using string = std::string;
}
inline Opm::ECLGraph path grid;
initGraph(int argc, char* argv[]) path init;
path restart;
};
inline Opm::parameter::ParameterGroup
initParam(int argc, char** argv)
{ {
// Obtain parameters from command line (possibly specifying a parameter file). // Obtain parameters from command line (possibly specifying a parameter file).
const bool verify_commandline_syntax = true; const bool verify_commandline_syntax = true;
const bool parameter_output = false; const bool parameter_output = false;
Opm::parameter::ParameterGroup param(argc, argv, verify_commandline_syntax, parameter_output); Opm::parameter::ParameterGroup param(argc, argv, verify_commandline_syntax, parameter_output);
return param;
// Obtain filenames for grid, init and restart files, as well as step number.
using boost::filesystem::path;
using std::string;
const string casename = param.getDefault<string>("case", "DEFAULT_CASE_NAME");
const path grid = param.has("grid") ? param.get<string>("grid")
: deriveFileName(casename, { ".EGRID", ".FEGRID", ".GRID", ".FGRID" });
const path init = param.has("init") ? param.get<string>("init")
: deriveFileName(casename, { ".INIT", ".FINIT" });
const path restart = param.has("restart") ? param.get<string>("restart")
: deriveFileName(casename, { ".UNRST", ".FUNRST" });
const int step = param.getDefault("step", 0);
// Read graph and fluxes, initialise the toolbox.
auto graph = Opm::ECLGraph::load(grid, init);
graph.assignFluxDataSource(restart);
if (! graph.selectReportStep(step)) {
std::ostringstream os;
os << "Report Step " << step
<< " is Not Available in Result Set '"
<< grid.stem() << '\'';
throw std::domain_error(os.str());
} }
inline Opm::ECLGraph
initGraph(const FilePaths& file_paths)
{
// Read graph and assign restart file.
auto graph = Opm::ECLGraph::load(file_paths.grid, file_paths.init);
graph.assignFluxDataSource(file_paths.restart);
return graph; return graph;
} }
inline std::vector<Opm::ECLWellSolution::WellData>
initWellFluxes(const Opm::ECLGraph& G)
{
auto wsol = Opm::ECLWellSolution{};
return wsol.solution(G.rawResultData(), G.numGrids());
}
inline Opm::FlowDiagnostics::Toolbox inline Opm::FlowDiagnostics::Toolbox
initToolbox(const Opm::ECLGraph& G, const std::vector<Opm::ECLWellSolution::WellData>& well_fluxes) initToolbox(const Opm::ECLGraph& G)
{ {
const auto connGraph = Opm::FlowDiagnostics:: const auto connGraph = Opm::FlowDiagnostics::
ConnectivityGraph{ static_cast<int>(G.numCells()), ConnectivityGraph{ static_cast<int>(G.numCells()),
@@ -211,11 +193,7 @@ namespace example {
// Create the Toolbox. // Create the Toolbox.
auto tool = Opm::FlowDiagnostics::Toolbox{ connGraph }; auto tool = Opm::FlowDiagnostics::Toolbox{ connGraph };
tool.assignPoreVolume(G.poreVolume()); tool.assignPoreVolume(G.poreVolume());
tool.assignConnectionFlux(Hack::convert_flux_to_SI(extractFluxField(G)));
tool.assignInflowFlux(extractWellFlows(G, well_fluxes));
return tool; return tool;
} }
@@ -226,15 +204,42 @@ namespace example {
struct Setup struct Setup
{ {
Setup(int argc, char** argv) Setup(int argc, char** argv)
: graph(initGraph(argc, argv)) : param(initParam(argc, argv))
, well_fluxes(initWellFluxes(graph)) , file_paths(param)
, toolbox(initToolbox(graph, well_fluxes)) , graph(initGraph(file_paths))
, well_fluxes()
, toolbox(initToolbox(graph))
, compute_fluxes_(param.getDefault("compute_fluxes", false))
{ {
const int step = param.getDefault("step", 0);
if (!selectReportStep(step)) {
std::ostringstream os;
os << "Report Step " << step
<< " is Not Available in Result Set '"
<< file_paths.grid.stem() << '\'';
throw std::domain_error(os.str());
}
} }
bool selectReportStep(const int step)
{
if (graph.selectReportStep(step)) {
auto wsol = Opm::ECLWellSolution{};
well_fluxes = wsol.solution(graph.rawResultData(), graph.numGrids());;
toolbox.assignConnectionFlux(extractFluxField(graph, compute_fluxes_));
toolbox.assignInflowFlux(extractWellFlows(graph, well_fluxes));
return true;
} else {
return false;
}
}
Opm::parameter::ParameterGroup param;
FilePaths file_paths;
Opm::ECLGraph graph; Opm::ECLGraph graph;
std::vector<Opm::ECLWellSolution::WellData> well_fluxes; std::vector<Opm::ECLWellSolution::WellData> well_fluxes;
Opm::FlowDiagnostics::Toolbox toolbox; Opm::FlowDiagnostics::Toolbox toolbox;
bool compute_fluxes_ = false;
}; };

View File

@@ -0,0 +1,64 @@
/*
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/ECLFluxCalc.hpp>
namespace Opm
{
ECLFluxCalc::ECLFluxCalc(const ECLGraph& graph)
: graph_(graph)
, neighbours_(graph.neighbours())
, transmissibility_(graph.transmissibility())
{
}
std::vector<double> ECLFluxCalc::flux(const PhaseIndex /* phase */) const
{
// Obtain pressure vector.
std::vector<double> pressure = graph_.linearisedCellData("PRESSURE", &ECLUnits::UnitSystem::pressure);
// Compute fluxes per connection.
const int num_conn = transmissibility_.size();
std::vector<double> fluxvec(num_conn);
for (int conn = 0; conn < num_conn; ++conn) {
fluxvec[conn] = singleFlux(conn, pressure);
}
return fluxvec;
}
double ECLFluxCalc::singleFlux(const int connection,
const std::vector<double>& pressure) const
{
const int c1 = neighbours_[2*connection];
const int c2 = neighbours_[2*connection + 1];
const double t = transmissibility_[connection];
return t * (pressure[c1] - pressure[c2]);
}
} // namespace Opm

View File

@@ -0,0 +1,61 @@
/*
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_ECLFLUXCALC_HEADER_INCLUDED
#define OPM_ECLFLUXCALC_HEADER_INCLUDED
#include <opm/utility/ECLGraph.hpp>
#include <vector>
namespace Opm
{
/// Class for computing connection fluxes in the absence of flux output.
class ECLFluxCalc
{
public:
/// Construct from ECLGraph.
///
/// \param[in] graph Connectivity data, as well as providing a means to read data from the restart file.
explicit ECLFluxCalc(const ECLGraph& graph);
using PhaseIndex = ECLGraph::PhaseIndex;
/// Retrive phase flux on all connections defined by \code
/// graph.neighbours() \endcode.
///
/// \param[in] phase Canonical phase for which to retrive flux.
///
/// \return Flux values corresponding to selected phase.
/// Empty if required data is missing.
/// Numerical values in SI units (rm^3/s).
std::vector<double> flux(const PhaseIndex phase) const;
private:
double singleFlux(const int connection,
const std::vector<double>& pressure) const;
const ECLGraph& graph_;
std::vector<int> neighbours_;
std::vector<double> transmissibility_;
};
} // namespace Opm
#endif // OPM_ECLFLUXCALC_HEADER_INCLUDED

View File

@@ -24,6 +24,9 @@
#include <opm/utility/ECLGraph.hpp> #include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLResultData.hpp> #include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -41,6 +44,7 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <ert/ecl/ecl_grid.h> #include <ert/ecl/ecl_grid.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl/ecl_nnc_export.h> #include <ert/ecl/ecl_nnc_export.h>
#include <ert/util/ert_unique_ptr.hpp> #include <ert/util/ert_unique_ptr.hpp>
@@ -93,6 +97,18 @@ namespace {
std::array<std::size_t,3> std::array<std::size_t,3>
cartesianDimensions(const ecl_grid_type* G); cartesianDimensions(const ecl_grid_type* G);
/// Access unit conventions pertaining to single grid in result set.
///
/// \param[in] rset Result set.
///
/// \param[in] grid_ID Numerical ID of grid. Non-negative. Zero
/// for the main grid and positive for LGRs.
///
/// \return Unit system convention for \p grid_ID in result set.
auto getUnitSystem(const ::Opm::ECLResultData& rset,
const int grid_ID)
-> decltype(::Opm::ECLUnits::createUnitSystem(0));
/// Retrieve global pore-volume vector from INIT source. /// Retrieve global pore-volume vector from INIT source.
/// ///
/// Specialised tool needed to determine the active cells. /// Specialised tool needed to determine the active cells.
@@ -101,7 +117,11 @@ namespace {
/// ///
/// \param[in] init ERT representation of INIT source. /// \param[in] init ERT representation of INIT source.
/// ///
/// \return Vector of pore-volumes for all global cells of \p G. /// \param[in] grid_ID Numerical ID of grid. Non-negative. Zero
/// for the main grid and positive in the case of an LGR.
///
/// \return Vector of pore-volumes for all global cells of \p G in
/// SI unit conventions (rm^3).
std::vector<double> std::vector<double>
getPVolVector(const ecl_grid_type* G, getPVolVector(const ecl_grid_type* G,
const ::Opm::ECLResultData& init, const ::Opm::ECLResultData& init,
@@ -137,6 +157,11 @@ namespace {
const ::Opm::ECLResultData& init, const ::Opm::ECLResultData& init,
const int gridID); const int gridID);
/// Retrieve non-negative numeric ID of grid instance.
///
/// \return Constructor's \c gridID parameter.
int gridID() const;
/// Retrieve number of active cells in graph. /// Retrieve number of active cells in graph.
std::size_t numCells() const; std::size_t numCells() const;
@@ -154,9 +179,18 @@ namespace {
/// ///
/// Corresponds to the \c PORV vector in the INIT file, possibly /// Corresponds to the \c PORV vector in the INIT file, possibly
/// restricted to those active cells for which the pore-volume is /// restricted to those active cells for which the pore-volume is
/// strictly positive. /// strictly positive. SI unit conventions (rm^3).
const std::vector<double>& activePoreVolume() const; const std::vector<double>& activePoreVolume() const;
/// Retrieve static (background) transmissibility values on all
/// connections defined by \code neighbours() \endcode.
///
/// Specifically, \code transmissibility()[i] \endcode is the
/// transmissibility of the connection between cells \code
/// neighbours()[2*i + 0] \endcode and \code neighbours()[2*i +
/// 1] \endcode.
const std::vector<double>& transmissibility() const;
/// Retrieve ID of active cell from global ID. /// Retrieve ID of active cell from global ID.
int activeCell(const std::size_t globalCell) const; int activeCell(const std::size_t globalCell) const;
@@ -188,6 +222,11 @@ namespace {
cellData(const ::Opm::ECLResultData& src, cellData(const ::Opm::ECLResultData& src,
const std::string& vector) const; const std::string& vector) const;
template <typename T>
std::vector<T>
activeCellData(const ::Opm::ECLResultData& src,
const std::string& vector) const;
/// Retrieve values of result set vector for all Cartesian /// Retrieve values of result set vector for all Cartesian
/// connections in grid. /// connections in grid.
/// ///
@@ -200,7 +239,8 @@ namespace {
/// all of the grid's Cartesian connections. /// all of the grid's Cartesian connections.
std::vector<double> std::vector<double>
connectionData(const ::Opm::ECLResultData& src, connectionData(const ::Opm::ECLResultData& src,
const std::string& vector) const; const std::string& vector,
const double unit) const;
private: private:
/// Facility for deriving Cartesian neighbourship in a grid /// Facility for deriving Cartesian neighbourship in a grid
@@ -217,14 +257,18 @@ namespace {
/// \param[in] G ERT Grid representation. /// \param[in] G ERT Grid representation.
/// ///
/// \param[in] pvol Vector of pore-volumes on all global /// \param[in] pvol Vector of pore-volumes on all global
/// cells of \p G. Typically obtained /// cells of \p G. Typically obtained through function
/// through function getPVolVector(). /// getPVolVector(). Numerical values assumed to be in
/// SI units (rm^3).
CartesianCells(const ecl_grid_type* G, CartesianCells(const ecl_grid_type* G,
const std::vector<double>& pvol); const std::vector<double>& pvol);
/// Retrive global cell indices of all active cells in grid. /// Retrive global cell indices of all active cells in grid.
std::vector<std::size_t> activeGlobal() const; std::vector<std::size_t> activeGlobal() const;
/// Retrieve pore-volume values for all active cells in grid.
///
/// SI unit conventions (rm^3).
const std::vector<double>& activePoreVolume() const; const std::vector<double>& activePoreVolume() const;
/// Map input vector to all global cells. /// Map input vector to all global cells.
@@ -240,6 +284,30 @@ namespace {
std::vector<T> std::vector<T>
scatterToGlobal(const std::vector<T>& x) const; scatterToGlobal(const std::vector<T>& x) const;
/// Restrict input vector to active grid cells.
///
/// Selects subsets corresponding to active cells (i.e.,
/// those cells for which \code pore_volume > 0 \endcode) if
/// input size is
///
/// - All global cells
/// - Explicitly active cells (ACTNUM != 0)
///
/// All other cases are returned unfiltered--i.e., as direct
/// copies of the input.
///
/// \param[in] x Input vector, defined on the explicitly
/// active cells, all global cells or some
/// other subset (e.g., all non-neighbouring
/// connections).
///
/// \return Input vector restricted to active cells if
/// subset known. Direct copy if \p x is defined on set
/// other than explicitly active or all global cells.
template <typename T>
std::vector<T>
gatherToActive(const std::vector<T>& x) const;
/// Retrieve total number of cells in grid, including /// Retrieve total number of cells in grid, including
/// inactive ones. /// inactive ones.
/// ///
@@ -315,13 +383,8 @@ namespace {
/// Whether or not a particular active cell is subdivided. /// Whether or not a particular active cell is subdivided.
std::vector<bool> is_divided_; std::vector<bool> is_divided_;
/// Identify those grid cells that are further subdivided by /// Retrieve number of active cells in grid.
/// an LGR. std::size_t numActiveCells() const;
///
/// Writes to \c is_divided_.
///
/// \param
void identifySubdividedCells(const ecl_grid_type* G);
/// Compute linear index of global cell from explicit /// Compute linear index of global cell from explicit
/// (I,J,K) tuple. /// (I,J,K) tuple.
@@ -374,6 +437,11 @@ namespace {
/// Source cells for each Cartesian connection. /// Source cells for each Cartesian connection.
OutCell outCell_; OutCell outCell_;
/// Transmissibility field for purpose of on-demand flux
/// calculation if fluxes are not already available in dynamic
/// result set.
std::vector<double> trans_;
/// Predicate for whether or not a particular result vector is /// Predicate for whether or not a particular result vector is
/// defined on the grid's cells. /// defined on the grid's cells.
/// ///
@@ -415,6 +483,7 @@ namespace {
void connectionData(const ::Opm::ECLResultData& src, void connectionData(const ::Opm::ECLResultData& src,
const CartesianCells::Direction d, const CartesianCells::Direction d,
const std::string& vector, const std::string& vector,
const double unit,
std::vector<double>& x) const; std::vector<double>& x) const;
/// Form complete name of directional result set vector from /// Form complete name of directional result set vector from
@@ -430,7 +499,7 @@ namespace {
const CartesianCells::Direction d) const; const CartesianCells::Direction d) const;
/// Derive neighbourship relations on active cells in particular /// Derive neighbourship relations on active cells in particular
/// Cartesian directions. /// Cartesian directions and capture transmissibilty field.
/// ///
/// Writes to \c neigh_ and \c outCell_. /// Writes to \c neigh_ and \c outCell_.
/// ///
@@ -487,6 +556,13 @@ ECL::getPVolVector(const ecl_grid_type* G,
assert ((pvol.size() == nglob) && assert ((pvol.size() == nglob) &&
"Pore-volume must be provided for all global cells"); "Pore-volume must be provided for all global cells");
const auto pvol_unit =
getUnitSystem(init, gridID)->reservoirVolume();
for (auto& pv : pvol) {
pv = ::Opm::unit::convert::from(pv, pvol_unit);
}
} }
return pvol; return pvol;
@@ -524,6 +600,18 @@ ECL::cartesianDimensions(const ecl_grid_type* G)
make_szt(ecl_grid_get_nz(G)) } }; make_szt(ecl_grid_get_nz(G)) } };
} }
auto ECL::getUnitSystem(const ::Opm::ECLResultData& rset,
const int grid_ID)
-> decltype(::Opm::ECLUnits::createUnitSystem(0))
{
assert (rset.haveKeywordData(INTEHEAD_KW, grid_ID)
&& "Result Set Does Not Provide Grid Header");
const auto ih = rset.keywordData<int>(INTEHEAD_KW, grid_ID);
return ::Opm::ECLUnits::createUnitSystem(ih[INTEHEAD_UNIT_INDEX]);
}
std::vector<ecl_nnc_type> std::vector<ecl_nnc_type>
ECL::loadNNC(const ecl_grid_type* G, ECL::loadNNC(const ecl_grid_type* G,
const ::Opm::ECLResultData& init) const ::Opm::ECLResultData& init)
@@ -613,7 +701,7 @@ std::vector<std::size_t>
ECL::CartesianGridData::CartesianCells::activeGlobal() const ECL::CartesianGridData::CartesianCells::activeGlobal() const
{ {
auto active = std::vector<std::size_t>{}; auto active = std::vector<std::size_t>{};
active.reserve(this->rsMap_.subset.size()); active.reserve(this->numActiveCells());
for (const auto& id : this->rsMap_.subset) { for (const auto& id : this->rsMap_.subset) {
active.push_back(id.glob); active.push_back(id.glob);
@@ -656,6 +744,48 @@ CartesianCells::scatterToGlobal(const std::vector<T>& x) const
return y; return y;
} }
namespace { namespace ECL {
template <typename T>
std::vector<T>
CartesianGridData::CartesianCells::
gatherToActive(const std::vector<T>& x) const
{
const auto num_explicit_active =
static_cast<decltype(x.size())>(this->rsMap_.num_active);
if (x.size() == num_explicit_active) {
// Input defined on explictly active cells (ACTNUM != 0).
// Extract subset of these.
auto ax = std::vector<T>{};
ax.reserve(this->numActiveCells());
for (const auto& i : this->rsMap_.subset) {
ax.push_back(x[i.act]);
}
return ax;
}
if (x.size() == this->numGlobalCells()) {
// Input defined on all global cells. Extract active subset.
auto ax = std::vector<T>{};
ax.reserve(this->numActiveCells());
for (const auto& i : this->rsMap_.subset) {
ax.push_back(x[i.glob]);
}
return ax;
}
// Input defined on neither explicitly active nor global cells.
// Possibly on all grid's NNCs. Let caller deal with this.
return x;
}
}} // namespace Anonymous::ECL
std::size_t std::size_t
ECL::CartesianGridData::CartesianCells::numGlobalCells() const ECL::CartesianGridData::CartesianCells::numGlobalCells() const
{ {
@@ -718,6 +848,12 @@ ECL::CartesianGridData::CartesianCells::isSubdivided(const int cellID) const
return this->is_divided_[ix]; return this->is_divided_[ix];
} }
std::size_t
ECL::CartesianGridData::CartesianCells::numActiveCells() const
{
return this->rsMap_.subset.size();
}
std::size_t std::size_t
ECL::CartesianGridData:: ECL::CartesianGridData::
CartesianCells::globIdx(const IndexTuple& ijk) const CartesianCells::globIdx(const IndexTuple& ijk) const
@@ -772,6 +908,7 @@ CartesianGridData(const ecl_grid_type* G,
// Too large, but this is a quick estimate. // Too large, but this is a quick estimate.
this->neigh_.reserve(3 * (2 * this->numCells())); this->neigh_.reserve(3 * (2 * this->numCells()));
this->trans_.reserve(3 * (1 * this->numCells()));
for (const auto d : { CartesianCells::Direction::I , for (const auto d : { CartesianCells::Direction::I ,
CartesianCells::Direction::J , CartesianCells::Direction::J ,
@@ -781,6 +918,11 @@ CartesianGridData(const ecl_grid_type* G,
} }
} }
int ECL::CartesianGridData::gridID() const
{
return this->gridID_;
}
std::size_t std::size_t
ECL::CartesianGridData::numCells() const ECL::CartesianGridData::numCells() const
{ {
@@ -805,6 +947,12 @@ ECL::CartesianGridData::activePoreVolume() const
return this->cells_.activePoreVolume(); return this->cells_.activePoreVolume();
} }
const std::vector<double>&
ECL::CartesianGridData::transmissibility() const
{
return this->trans_;
}
int int
ECL::CartesianGridData::activeCell(const std::size_t globalCell) const ECL::CartesianGridData::activeCell(const std::size_t globalCell) const
{ {
@@ -839,6 +987,22 @@ cellData(const ::Opm::ECLResultData& src,
return this->cells_.scatterToGlobal(x); return this->cells_.scatterToGlobal(x);
} }
namespace { namespace ECL {
template <typename T>
std::vector<T>
CartesianGridData::activeCellData(const ::Opm::ECLResultData& src,
const std::string& vector) const
{
if (! this->haveCellData(src, vector)) {
return {};
}
auto x = src.keywordData<T>(vector, this->gridID_);
return this->cells_.gatherToActive(std::move(x));
}
}} // namespace Anonymous::ECL
bool bool
ECL::CartesianGridData:: ECL::CartesianGridData::
haveCellData(const ::Opm::ECLResultData& src, haveCellData(const ::Opm::ECLResultData& src,
@@ -871,7 +1035,8 @@ haveConnData(const ::Opm::ECLResultData& src,
std::vector<double> std::vector<double>
ECL::CartesianGridData:: ECL::CartesianGridData::
connectionData(const ::Opm::ECLResultData& src, connectionData(const ::Opm::ECLResultData& src,
const std::string& vector) const const std::string& vector,
const double unit) const
{ {
if (! this->haveConnData(src, vector)) { if (! this->haveConnData(src, vector)) {
return {}; return {};
@@ -883,7 +1048,7 @@ connectionData(const ::Opm::ECLResultData& src,
CartesianCells::Direction::J , CartesianCells::Direction::J ,
CartesianCells::Direction::K }) CartesianCells::Direction::K })
{ {
this->connectionData(src, d, vector, x); this->connectionData(src, d, this->vectorName(vector, d), unit, x);
} }
return x; return x;
@@ -894,10 +1059,10 @@ ECL::CartesianGridData::
connectionData(const ::Opm::ECLResultData& src, connectionData(const ::Opm::ECLResultData& src,
const CartesianCells::Direction d, const CartesianCells::Direction d,
const std::string& vector, const std::string& vector,
const double unit,
std::vector<double>& x) const std::vector<double>& x) const
{ {
const auto vname = this->vectorName(vector, d); const auto v = this->cellData(src, vector);
const auto v = this->cellData(src, vname);
const auto& cells = this->outCell_.find(d); const auto& cells = this->outCell_.find(d);
@@ -905,7 +1070,7 @@ connectionData(const ::Opm::ECLResultData& src,
"Direction must be I, J, or K"); "Direction must be I, J, or K");
for (const auto& cell : cells->second) { for (const auto& cell : cells->second) {
x.push_back(v[cell]); x.push_back(::Opm::unit::convert::from(v[cell], unit));
} }
} }
@@ -951,6 +1116,14 @@ deriveNeighbours(const std::vector<std::size_t>& gcells,
? this->cellData(init, tran) ? this->cellData(init, tran)
: std::vector<double>(this->cells_.numGlobalCells(), 1.0); : std::vector<double>(this->cells_.numGlobalCells(), 1.0);
const auto trans_unit =
ECL::getUnitSystem(init, this->gridID_)->transmissibility();
auto SI_trans = [trans_unit](const double trans)
{
return ::Opm::unit::convert::from(trans, trans_unit);
};
auto& ocell = this->outCell_[d]; auto& ocell = this->outCell_[d];
ocell.reserve(gcells.size()); ocell.reserve(gcells.size());
@@ -976,6 +1149,7 @@ deriveNeighbours(const std::vector<std::size_t>& gcells,
this->neigh_.push_back(other); this->neigh_.push_back(other);
ocell.push_back(globID); ocell.push_back(globID);
this->trans_.push_back(SI_trans(T[globID]));
} }
} }
} }
@@ -1032,28 +1206,57 @@ public:
/// Retrieve number of active cells in graph. /// Retrieve number of active cells in graph.
std::size_t numCells() const; std::size_t numCells() const;
/// Retrive number of connections in graph. /// Retrieve number of connections in graph.
std::size_t numConnections() const; std::size_t numConnections() const;
/// Retrive neighbourship relations between active cells. /// Retrieve the simulation scenario's active phases.
///
/// Mostly useful to determine the set of \c PhaseIndex values for which
/// flux() may return non-zero values.
const std::vector<PhaseIndex>& activePhases() const;
/// Retrieve neighbourship relations between active cells.
/// ///
/// The \c i-th connection is between active cells \code /// The \c i-th connection is between active cells \code
/// neighbours()[2*i + 0] \endcode and \code neighbours()[2*i + 1] /// neighbours()[2*i + 0] \endcode and \code neighbours()[2*i + 1]
/// \endcode. /// \endcode.
std::vector<int> neighbours() const; std::vector<int> neighbours() const;
/// Retrive static pore-volume values on active cells only. /// Retrieve static pore-volume values on active cells only.
/// ///
/// Corresponds to the \c PORV vector in the INIT file, possibly /// Corresponds to the \c PORV vector in the INIT file, possibly
/// restricted to those active cells for which the pore-volume is /// restricted to those active cells for which the pore-volume is
/// strictly positive. /// strictly positive.
std::vector<double> activePoreVolume() const; std::vector<double> activePoreVolume() const;
const ::Opm::ECLResultData& rawResultData() const; /// Retrieve static (background) transmissibility values on all
/// connections defined by \code neighbours() \endcode.
///
/// Specifically, \code transmissibility()[i] \endcode is the
/// transmissibility of the connection between cells \code
/// neighbours()[2*i + 0] \endcode and \code neighbours()[2*i + 1]
/// \endcode.
std::vector<double> transmissibility() const;
/// Restrict dynamic result set data to single report step.
///
/// This method must be called before calling either flux() or
/// rawResultData().
///
/// \param[in] rptstep Selected temporal vector. Report-step ID.
///
/// \return Whether or not dynamic data for the requested report step
/// exists in the underlying result set identified in method
/// assignDataSource().
bool selectReportStep(const int rptstep) const; bool selectReportStep(const int rptstep) const;
/// Retrive phase flux on all connections defined by \code neighbours() /// Access underlying result set.
///
/// The result set dynamic data corresponds to the most recent call to
/// method selectReportStep().
const ::Opm::ECLResultData& rawResultData() const;
/// Retrieve phase flux on all connections defined by \code neighbours()
/// \endcode. /// \endcode.
/// ///
/// \param[in] phase Canonical phase for which to retrive flux. /// \param[in] phase Canonical phase for which to retrive flux.
@@ -1068,6 +1271,14 @@ public:
std::vector<double> std::vector<double>
flux(const PhaseIndex phase) const; flux(const PhaseIndex phase) const;
template <typename T>
std::vector<T>
rawLinearisedCellData(const std::string& vector) const;
std::vector<double>
linearisedCellData(const std::string& vector,
UnitConvention unit) const;
private: private:
/// Collection of non-Cartesian neighbourship relations attributed to a /// Collection of non-Cartesian neighbourship relations attributed to a
/// particular ECL keyword set (i.e., one of NNC{1,2}, NNC{G,L}, NNCLL). /// particular ECL keyword set (i.e., one of NNC{1,2}, NNC{G,L}, NNCLL).
@@ -1186,9 +1397,14 @@ private:
/// \param[in] offset Start index into global linear number for all /// \param[in] offset Start index into global linear number for all
/// active grids. /// active grids.
/// ///
/// \param[in] trans_unit Unit of measurement of transmissibility
/// field stored in result set. Used to convert values to the
/// strict SI conventions (i.e., rm^3).
///
/// \param[in] nnc Non-neighbouring connection from result set. /// \param[in] nnc Non-neighbouring connection from result set.
void add(const std::vector<ECL::CartesianGridData>& grids, void add(const std::vector<ECL::CartesianGridData>& grids,
const std::vector<std::size_t>& offset, const std::vector<std::size_t>& offset,
const double trans_unit,
const ecl_nnc_type& nnc); const ecl_nnc_type& nnc);
std::vector<Category> allCategories() const; std::vector<Category> allCategories() const;
@@ -1199,6 +1415,10 @@ private:
/// Access all active non-neighbouring connections. /// Access all active non-neighbouring connections.
const std::vector<int>& getNeighbours() const; const std::vector<int>& getNeighbours() const;
/// Access transmissibility field of all active non-neighbouring
/// connections. Numerical values in strict SI units (rm^3).
const std::vector<double>& transmissibility() const;
/// Retrieve all non-neighbouring connections of a particular /// Retrieve all non-neighbouring connections of a particular
/// category (i.e., pertaining to a particular set of keywords). /// category (i.e., pertaining to a particular set of keywords).
/// ///
@@ -1214,6 +1434,13 @@ private:
/// in linear numbering of all model's active cells. /// in linear numbering of all model's active cells.
std::vector<int> neigh_; std::vector<int> neigh_;
/// Transmissibility of non-Cartesian (non-neighbouring) connections.
///
/// Note that \code trans_[i] \endcode is the transmissibility of
/// the connection between cells \code neigh_[2*i + 0] \endcode and
/// \code neigh_[2*i + 1] \endcode.
std::vector<double> trans_;
/// Collection of /// Collection of
KeywordIndexMap keywords_; KeywordIndexMap keywords_;
@@ -1221,7 +1448,9 @@ private:
/// ///
/// Simplifies implementation of ctor. /// Simplifies implementation of ctor.
/// ///
/// \param[in] cat Class /// \param[in] cat Requested category of flux relation.
///
/// \return Flux relation of type \p cat.
FluxRelation makeRelation(const Category cat) const; FluxRelation makeRelation(const Category cat) const;
/// Identify connection category from connection's grids. /// Identify connection category from connection's grids.
@@ -1284,6 +1513,11 @@ private:
/// range \code [0 .. numCells()) \endcode). /// range \code [0 .. numCells()) \endcode).
std::vector<std::size_t> activeOffset_; std::vector<std::size_t> activeOffset_;
/// Set of active phases in result set. Derived from .INIT on the
/// assumption that the set of active phases does not change throughout
/// the simulation run.
std::vector<PhaseIndex> activePhases_;
/// Current result set. /// Current result set.
std::unique_ptr<ECLResultData> src_; std::unique_ptr<ECLResultData> src_;
@@ -1299,6 +1533,11 @@ private:
void defineNNCs(const ecl_grid_type* G, void defineNNCs(const ecl_grid_type* G,
const ::Opm::ECLResultData& init); const ::Opm::ECLResultData& init);
/// Extract scenario's set of active phases.
///
/// Writes to activePhases_.
void defineActivePhases(const ::Opm::ECLResultData& init);
/// Compute ECL vector basename for particular phase flux. /// Compute ECL vector basename for particular phase flux.
/// ///
/// \param[in] phase Canonical phase for which to derive ECL vector /// \param[in] phase Canonical phase for which to derive ECL vector
@@ -1312,15 +1551,29 @@ private:
/// Extract flux values corresponding to particular result set vector /// Extract flux values corresponding to particular result set vector
/// for all identified non-neighbouring connections. /// for all identified non-neighbouring connections.
/// ///
/// \tparam[in] GetFluxUnit Type of function object for computing the
/// grid-dependent flux unit.
///
/// \param[in] vector Result set vector prefix. Typically computed by /// \param[in] vector Result set vector prefix. Typically computed by
/// method flowVector(). /// method flowVector().
/// ///
/// \param[in] fluxUnit Function object for computing grid-dependent
/// flux units. Must support the syntax
/// \code
/// unit = fluxUnit(gridID)
/// \endcode
/// with 'gridID' being a non-negative \c int that identifies a
/// particular model grid (zero for the main grid and positive for
/// LGRs) and 'unit' a positive floating-point value.
///
/// \param[in,out] flux Numerical values of result set vector. On /// \param[in,out] flux Numerical values of result set vector. On
/// input, contains all values corresponding to all fully Cartesian /// input, contains all values corresponding to all fully Cartesian
/// connections across all active grids. On output additionally /// connections across all active grids. On output additionally
/// contains those values that correspond to the non-neighbouring /// contains those values that correspond to the non-neighbouring
/// connections (appended onto \p flux). /// connections (appended onto \p flux).
template <class GetFluxUnit>
void fluxNNC(const std::string& vector, void fluxNNC(const std::string& vector,
GetFluxUnit&& fluxUnit,
std::vector<double>& flux) const; std::vector<double>& flux) const;
}; };
@@ -1371,6 +1624,7 @@ void
Opm::ECLGraph::Impl:: Opm::ECLGraph::Impl::
NNC::add(const std::vector<ECL::CartesianGridData>& grid, NNC::add(const std::vector<ECL::CartesianGridData>& grid,
const std::vector<std::size_t>& offset, const std::vector<std::size_t>& offset,
const double trans_unit,
const ecl_nnc_type& nnc) const ecl_nnc_type& nnc)
{ {
if (! this->isViable(grid, nnc)) { if (! this->isViable(grid, nnc)) {
@@ -1395,6 +1649,10 @@ NNC::add(const std::vector<ECL::CartesianGridData>& grid,
this->neigh_.push_back(o + c); this->neigh_.push_back(o + c);
} }
// Capture transmissibility field to support on-demand flux calculations
// if flux fields are not output to the on-disk result set.
this->trans_.push_back(unit::convert::from(nnc.trans, trans_unit));
const auto cat = this->classifyConnection(nnc.grid_nr1, nnc.grid_nr2); const auto cat = this->classifyConnection(nnc.grid_nr1, nnc.grid_nr2);
auto entry = NonNeighKeywordIndexSet::Map { auto entry = NonNeighKeywordIndexSet::Map {
@@ -1411,9 +1669,10 @@ NNC::add(const std::vector<ECL::CartesianGridData>& grid,
std::size_t std::size_t
Opm::ECLGraph::Impl::NNC::numConnections() const Opm::ECLGraph::Impl::NNC::numConnections() const
{ {
assert (this->neigh_.size() % 2 == 0); assert ((this->neigh_.size() % 2) == 0);
assert ((this->neigh_.size() / 2) == this->trans_.size());
return this->neigh_.size() / 2; return this->trans_.size();
} }
const std::vector<int>& const std::vector<int>&
@@ -1422,6 +1681,12 @@ Opm::ECLGraph::Impl::NNC::getNeighbours() const
return this->neigh_; return this->neigh_;
} }
const std::vector<double>&
Opm::ECLGraph::Impl::NNC::transmissibility() const
{
return this->trans_;
}
const Opm::ECLGraph::Impl::NNC::FluxRelation& const Opm::ECLGraph::Impl::NNC::FluxRelation&
Opm::ECLGraph::Impl::NNC::getRelations(const Category& cat) const Opm::ECLGraph::Impl::NNC::getRelations(const Category& cat) const
{ {
@@ -1521,6 +1786,7 @@ Opm::ECLGraph::Impl::Impl(const Path& grid, const Path& init)
} }
this->defineNNCs(G.get(), I); this->defineNNCs(G.get(), I);
this->defineActivePhases(I);
} }
void void
@@ -1579,6 +1845,12 @@ Opm::ECLGraph::Impl::numConnections() const
return nconn + this->nnc_.numConnections(); return nconn + this->nnc_.numConnections();
} }
const std::vector<Opm::ECLGraph::PhaseIndex>&
Opm::ECLGraph::Impl::activePhases() const
{
return this->activePhases_;
}
std::vector<int> std::vector<int>
Opm::ECLGraph::Impl::neighbours() const Opm::ECLGraph::Impl::neighbours() const
{ {
@@ -1625,6 +1897,34 @@ Opm::ECLGraph::Impl::activePoreVolume() const
return pvol; return pvol;
} }
std::vector<double>
Opm::ECLGraph::Impl::transmissibility() const
{
auto trans = std::vector<double>{};
// Recall: this->numConnections() includes NNCs.
const auto totconn = this->numConnections();
trans.reserve(totconn);
for (const auto& G : this->grid_) {
const auto& Ti = G.transmissibility();
trans.insert(trans.end(), Ti.begin(), Ti.end());
}
if (this->nnc_.numConnections() > 0) {
const auto& tranNNC = this->nnc_.transmissibility();
trans.insert(trans.end(), tranNNC.begin(), tranNNC.end());
}
if (trans.size() < totconn) {
return {};
}
return trans;
}
const ::Opm::ECLResultData& const ::Opm::ECLResultData&
Opm::ECLGraph::Impl::rawResultData() const Opm::ECLGraph::Impl::rawResultData() const
{ {
@@ -1640,6 +1940,11 @@ std::vector<double>
Opm::ECLGraph::Impl:: Opm::ECLGraph::Impl::
flux(const PhaseIndex phase) const flux(const PhaseIndex phase) const
{ {
auto fluxUnit = [this](const int gridID)
{
return ::ECL::getUnitSystem(*this->src_, gridID)->reservoirRate();
};
const auto vector = this->flowVector(phase); const auto vector = this->flowVector(phase);
auto v = std::vector<double>{}; auto v = std::vector<double>{};
@@ -1650,7 +1955,8 @@ flux(const PhaseIndex phase) const
v.reserve(totconn); v.reserve(totconn);
for (const auto& G : this->grid_) { for (const auto& G : this->grid_) {
const auto& q = G.connectionData(*this->src_, vector); const auto& q =
G.connectionData(*this->src_, vector, fluxUnit(G.gridID()));
if (q.empty()) { if (q.empty()) {
// Flux vector invalid unless all grids provide this result // Flux vector invalid unless all grids provide this result
@@ -1665,7 +1971,7 @@ flux(const PhaseIndex phase) const
// Model includes non-neighbouring connections such as faults and/or // Model includes non-neighbouring connections such as faults and/or
// local grid refinement. Extract pertinent flux values for these // local grid refinement. Extract pertinent flux values for these
// connections. // connections.
this->fluxNNC(vector, v); this->fluxNNC(vector, std::move(fluxUnit), v);
} }
if (v.size() < totconn) { if (v.size() < totconn) {
@@ -1677,17 +1983,81 @@ flux(const PhaseIndex phase) const
return v; return v;
} }
namespace Opm {
template <typename T>
std::vector<T>
ECLGraph::Impl::rawLinearisedCellData(const std::string& vector) const
{
auto x = std::vector<T>{}; x.reserve(this->numCells());
for (const auto& G : this->grid_) {
const auto xi = G.activeCellData<T>(*this->src_, vector);
x.insert(x.end(), std::begin(xi), std::end(xi));
}
if (x.size() != this->numCells()) {
return {};
}
return x;
}
} // namespace Opm
std::vector<double>
Opm::ECLGraph::Impl::linearisedCellData(const std::string& vector,
UnitConvention unit) const
{
auto x = std::vector<double>{}; x.reserve(this->numCells());
for (const auto& G : this->grid_) {
const auto xi = G.activeCellData<double>(*this->src_, vector);
if (xi.empty()) { continue; }
// Note: Compensate for incrementing Grid ID above.
const auto usys =
ECL::getUnitSystem(*this->src_, G.gridID());
// Note: 'usys' (generally, std::unique_ptr<>) does not support
// regular PMF syntax (i.e., operator->*()).
const auto vector_unit = ((*usys).*unit)();
std::transform(std::begin(xi), std::end(xi),
std::back_inserter(x),
[vector_unit](const double value)
{
return ::Opm::unit::convert::from(value, vector_unit);
});
}
if (x.size() != this->numCells()) {
return {};
}
return x;
}
void void
Opm::ECLGraph::Impl::defineNNCs(const ecl_grid_type* G, Opm::ECLGraph::Impl::defineNNCs(const ecl_grid_type* G,
const ::Opm::ECLResultData& init) const ::Opm::ECLResultData& init)
{ {
// Assume all transmissibilites in the result set follow the same unit
// conventions.
const auto trans_unit =
ECL::getUnitSystem(init, 0)->transmissibility();
for (const auto& nnc : ECL::loadNNC(G, init)) { for (const auto& nnc : ECL::loadNNC(G, init)) {
this->nnc_.add(this->grid_, this->activeOffset_, nnc); this->nnc_.add(this->grid_, this->activeOffset_, trans_unit, nnc);
} }
} }
template <class GetFluxUnit>
void void
Opm::ECLGraph::Impl::fluxNNC(const std::string& vector, Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
GetFluxUnit&& fluxUnit,
std::vector<double>& flux) const std::vector<double>& flux) const
{ {
auto v = std::vector<double>(this->nnc_.numConnections(), 0.0); auto v = std::vector<double>(this->nnc_.numConnections(), 0.0);
@@ -1697,13 +2067,11 @@ Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
const auto& rel = this->nnc_.getRelations(cat); const auto& rel = this->nnc_.getRelations(cat);
const auto fluxID = rel.makeKeyword(vector); const auto fluxID = rel.makeKeyword(vector);
auto gridID = 0;
for (const auto& G : this->grid_) { for (const auto& G : this->grid_) {
const auto& iset = rel.indexSet().getGridCollection(gridID); const auto gridID = G.gridID();
// Must increment grid ID irrespective of early break of const auto& iset =
// iteration. rel.indexSet().getGridCollection(gridID);
gridID += 1;
if (iset.empty()) { if (iset.empty()) {
// No NNCs for this category in this grid. Skip. // No NNCs for this category in this grid. Skip.
@@ -1719,13 +2087,16 @@ Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
continue; continue;
} }
const auto flux_unit = fluxUnit(gridID);
// Data fully available for (category,gridID). Assign // Data fully available for (category,gridID). Assign
// approriate subset of NNC flux vector. // approriate subset of NNC flux vector.
for (const auto& ix : iset) { for (const auto& ix : iset) {
assert (ix.neighIdx < v.size()); assert (ix.neighIdx < v.size());
assert (ix.kwIdx < q.size()); assert (ix.kwIdx < q.size());
v[ix.neighIdx] = q[ix.kwIdx]; v[ix.neighIdx] =
unit::convert::from(q[ix.kwIdx], flux_unit);
assigned[ix.neighIdx] = true; assigned[ix.neighIdx] = true;
} }
@@ -1745,6 +2116,32 @@ Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
} }
} }
void
Opm::ECLGraph::Impl::
defineActivePhases(const ::Opm::ECLResultData& init)
{
const auto ih =
init.keywordData<int>(INTEHEAD_KW, ECL_GRID_MAINGRID_LGR_NR);
const auto phaseMask =
static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]);
using Check = std::pair<PhaseIndex, unsigned int>;
const auto allPhases = std::vector<Check> {
{ PhaseIndex::Aqua , (1u << 1) },
{ PhaseIndex::Liquid, (1u << 0) },
{ PhaseIndex::Vapour, (1u << 2) },
};
this->activePhases_.clear();
for (const auto& phase : allPhases) {
if ((phase.second & phaseMask) != 0) {
this->activePhases_.push_back(phase.first);
}
}
}
std::string std::string
Opm::ECLGraph::Impl:: Opm::ECLGraph::Impl::
flowVector(const PhaseIndex phase) const flowVector(const PhaseIndex phase) const
@@ -1834,6 +2231,12 @@ Opm::ECLGraph::numConnections() const
return this->pImpl_->numConnections(); return this->pImpl_->numConnections();
} }
const std::vector<Opm::ECLGraph::PhaseIndex>&
Opm::ECLGraph::activePhases() const
{
return this->pImpl_->activePhases();
}
std::vector<int> Opm::ECLGraph::neighbours() const std::vector<int> Opm::ECLGraph::neighbours() const
{ {
return this->pImpl_->neighbours(); return this->pImpl_->neighbours();
@@ -1844,6 +2247,11 @@ std::vector<double> Opm::ECLGraph::poreVolume() const
return this->pImpl_->activePoreVolume(); return this->pImpl_->activePoreVolume();
} }
std::vector<double> Opm::ECLGraph::transmissibility() const
{
return this->pImpl_->transmissibility();
}
bool Opm::ECLGraph::selectReportStep(const int rptstep) const bool Opm::ECLGraph::selectReportStep(const int rptstep) const
{ {
return this->pImpl_->selectReportStep(rptstep); return this->pImpl_->selectReportStep(rptstep);
@@ -1861,3 +2269,28 @@ flux(const PhaseIndex phase) const
{ {
return this->pImpl_->flux(phase); return this->pImpl_->flux(phase);
} }
namespace Opm {
template <typename T>
std::vector<T>
ECLGraph::rawLinearisedCellData(const std::string& vector) const
{
return this->pImpl_->rawLinearisedCellData<T>(vector);
}
// Explicit instantiations for the element types we care about.
template std::vector<int>
ECLGraph::rawLinearisedCellData<int>(const std::string& vector) const;
template std::vector<double>
ECLGraph::rawLinearisedCellData<double>(const std::string& vector) const;
} // namespace Opm
std::vector<double>
Opm::ECLGraph::linearisedCellData(const std::string& vector,
UnitConvention unit) const
{
return this->pImpl_->linearisedCellData(vector, unit);
}

View File

@@ -22,6 +22,7 @@
#define OPM_ECLGRAPH_HEADER_INCLUDED #define OPM_ECLGRAPH_HEADER_INCLUDED
#include <opm/utility/ECLResultData.hpp> #include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
@@ -46,6 +47,9 @@ namespace Opm {
public: public:
using Path = boost::filesystem::path; using Path = boost::filesystem::path;
/// Enum for indicating requested phase from the flux() method.
enum class PhaseIndex { Aqua = 0, Liquid = 1, Vapour = 2 };
/// Disabled default constructor. /// Disabled default constructor.
ECLGraph() = delete; ECLGraph() = delete;
@@ -124,6 +128,12 @@ namespace Opm {
/// Retrieve number of connections in graph. /// Retrieve number of connections in graph.
std::size_t numConnections() const; std::size_t numConnections() const;
/// Retrieve the simulation scenario's active phases.
///
/// Mostly useful to determine the set of \c PhaseIndex values for
/// which flux() will return non-zero values if data available.
const std::vector<PhaseIndex>& activePhases() const;
/// Retrieve neighbourship relations between active cells. /// Retrieve neighbourship relations between active cells.
/// ///
/// The \c i-th connection is between active cells \code /// The \c i-th connection is between active cells \code
@@ -135,9 +145,18 @@ namespace Opm {
/// ///
/// Corresponds to the \c PORV vector in the INIT file, possibly /// Corresponds to the \c PORV vector in the INIT file, possibly
/// restricted to those active cells for which the pore-volume is /// restricted to those active cells for which the pore-volume is
/// strictly positive. /// strictly positive. Numerical values in SI units (rm^3).
std::vector<double> poreVolume() const; std::vector<double> poreVolume() const;
/// Retrieve static (background) transmissibility values on all
/// connections defined by \code neighbours() \endcode.
///
/// Specifically, \code transmissibility()[i] \endcode is the
/// transmissibility of the connection between cells \code
/// neighbours()[2*i + 0] \endcode and \code neighbours()[2*i + 1]
/// \endcode.
std::vector<double> transmissibility() const;
/// Restrict dynamic result set data to single report step. /// Restrict dynamic result set data to single report step.
/// ///
/// This method must be called before calling either flux() or /// This method must be called before calling either flux() or
@@ -156,21 +175,57 @@ namespace Opm {
/// to method selectReportStep(). /// to method selectReportStep().
const ::Opm::ECLResultData& rawResultData() const; const ::Opm::ECLResultData& rawResultData() const;
/// Enum for indicating requested phase from the flux() method. /// Retrieve phase flux on all connections defined by \code
enum class PhaseIndex { Aqua = 0, Liquid = 1, Vapour = 2 };
/// Retrive phase flux on all connections defined by \code
/// neighbours() \endcode. /// neighbours() \endcode.
/// ///
/// \param[in] phase Canonical phase for which to retrive flux. /// \param[in] phase Canonical phase for which to retrieve flux.
/// ///
/// \return Flux values corresponding to selected phase. Empty if /// \return Flux values corresponding to selected phase. Empty if
/// unavailable in the result set (e.g., when querying the gas /// unavailable in the result set (e.g., when querying the gas
/// flux in an oil/water system or if no flux values at all were /// flux in an oil/water system or if no flux values at all were
/// output to the restart file). /// output to the restart file). Numerical values in SI units
/// (rm^3/s).
std::vector<double> std::vector<double>
flux(const PhaseIndex phase) const; flux(const PhaseIndex phase) const;
/// Retrieve result set vector from current view (e.g., particular
/// report step) linearised on active cells.
///
/// \tparam T Element type of result set vector.
///
/// \param[in] vector Name of result set vector.
///
/// \return Result set vector linearised on active cells.
template <typename T>
std::vector<T>
rawLinearisedCellData(const std::string& vector) const;
/// Convenience type alias for \c UnitSystem PMFs (pointer to member
/// function).
typedef double (ECLUnits::UnitSystem::*UnitConvention)() const;
/// Retrieve floating-point result set vector from current view
/// (e.g., particular report step) linearised on active cells and
/// converted to strict SI unit conventions.
///
/// Typical call:
/// \code
/// const auto press =
/// lCD("PRESSURE", &ECLUnits::UnitSystem::pressure);
/// \endcode
///
/// \param[in] vector Name of result set vector.
///
/// \param[in] unit Call-back hook in \c UnitSystem implementation
/// that enables converting the raw result data to strict SI unit
/// conventions. Hook is called for each grid in the result set.
///
/// \return Result set vector linearised on active cells, converted
/// to strict SI unit conventions.
std::vector<double>
linearisedCellData(const std::string& vector,
UnitConvention unit) const;
private: private:
/// Implementation class. /// Implementation class.
class Impl; class Impl;

View File

@@ -0,0 +1,209 @@
/*
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
#include <opm/utility/ECLUnitHandling.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <exception>
#include <stdexcept>
#include <string>
#include <ert/ecl/ecl_util.h>
namespace Opm { namespace ECLUnits {
namespace Impl {
ert_ecl_unit_enum getUnitConvention(const int usys);
template <ert_ecl_unit_enum convention>
class USys;
template <>
class USys<ECL_METRIC_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double pressure() const override
{
return Metric::Pressure;
}
virtual double reservoirRate() const override
{
return Metric::ReservoirVolume / Metric::Time;
}
virtual double reservoirVolume() const override
{
return Metric::ReservoirVolume;
}
virtual double time() const override
{
return Metric::Time;
}
virtual double transmissibility() const override
{
return Metric::Transmissibility;
}
};
template <>
class USys<ECL_FIELD_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double pressure() const override
{
return Field::Pressure;
}
virtual double reservoirRate() const override
{
return Field::ReservoirVolume / Field::Time;
}
virtual double reservoirVolume() const override
{
return Field::ReservoirVolume;
}
virtual double time() const override
{
return Field::Time;
}
virtual double transmissibility() const override
{
return Field::Transmissibility;
}
};
template <>
class USys<ECL_LAB_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double pressure() const override
{
return Lab::Pressure;
}
virtual double reservoirRate() const override
{
return Lab::ReservoirVolume / Lab::Time;
}
virtual double reservoirVolume() const override
{
return Lab::ReservoirVolume;
}
virtual double time() const override
{
return Lab::Time;
}
virtual double transmissibility() const override
{
return Lab::Transmissibility;
}
};
template <>
class USys<ECL_PVT_M_UNITS> : public ::Opm::ECLUnits::UnitSystem
{
public:
virtual double pressure() const override
{
return unit::atm;
}
virtual double reservoirRate() const override
{
using namespace prefix;
using namespace unit;
return cubic(meter) / day;
}
virtual double reservoirVolume() const override
{
using namespace prefix;
using namespace unit;
return cubic(meter);
}
virtual double time() const override
{
return unit::day;
}
virtual double transmissibility() const override
{
using namespace prefix;
using namespace unit;
return centi*Poise * cubic(meter) / (day * atm);
}
};
} // namespace Impl
}} // namespace Opm::ECLUnits
ert_ecl_unit_enum
Opm::ECLUnits::Impl::getUnitConvention(const int usys)
{
switch (usys) {
case 1: return ECL_METRIC_UNITS;
case 2: return ECL_FIELD_UNITS;
case 3: return ECL_LAB_UNITS;
case 4: return ECL_PVT_M_UNITS;
}
throw std::runtime_error("Unsupported Unit Convention: "
+ std::to_string(usys));
}
std::unique_ptr<const ::Opm::ECLUnits::UnitSystem>
Opm::ECLUnits::createUnitSystem(const int usys)
{
using UPtr =
std::unique_ptr<const ::Opm::ECLUnits::UnitSystem>;
switch (Impl::getUnitConvention(usys)) {
case ECL_METRIC_UNITS:
return UPtr{ new Impl::USys<ECL_METRIC_UNITS>{} };
case ECL_FIELD_UNITS:
return UPtr{ new Impl::USys<ECL_FIELD_UNITS>{} };
case ECL_LAB_UNITS:
return UPtr{ new Impl::USys<ECL_LAB_UNITS>{} };
case ECL_PVT_M_UNITS:
return UPtr{ new Impl::USys<ECL_PVT_M_UNITS>{} };
}
throw std::runtime_error("Unsupported Unit Convention: "
+ std::to_string(usys));
}

View File

@@ -0,0 +1,45 @@
/*
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/>.
*/
#ifndef OPM_ECLUNITHANDLING_HEADER_INCLUDED
#define OPM_ECLUNITHANDLING_HEADER_INCLUDED
#include <memory>
namespace Opm {
namespace ECLUnits {
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;
};
std::unique_ptr<const UnitSystem>
createUnitSystem(const int usys);
} // namespace ECLUnits
} // namespace Opm
#endif // OPM_ECLUNITHANDLING_HEADER_INCLUDED

View File

@@ -24,6 +24,7 @@
#include <opm/utility/ECLWellSolution.hpp> #include <opm/utility/ECLWellSolution.hpp>
#include <opm/utility/ECLResultData.hpp> #include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLUnitHandling.hpp>
#include <opm/parser/eclipse/Units/Units.hpp> #include <opm/parser/eclipse/Units/Units.hpp>
#include <ert/ecl/ecl_kw_magic.h> #include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl_well/well_const.h> #include <ert/ecl_well/well_const.h>
@@ -108,15 +109,7 @@ namespace Opm
// Reservoir rate units from code used in INTEHEAD. // Reservoir rate units from code used in INTEHEAD.
double resRateUnit(const int unit_code) double resRateUnit(const int unit_code)
{ {
using prefix::centi; return ECLUnits::createUnitSystem(unit_code)->reservoirRate();
using namespace unit;
switch (unit_code) {
case INTEHEAD::Metric: return cubic(meter)/day; // m^3/day
case INTEHEAD::Field: return stb/day; // stb/day
case INTEHEAD::Lab: return cubic(centi*meter)/hour; // (cm)^3/h
default: throw std::runtime_error("Unknown unit code from INTEHEAD: " + std::to_string(unit_code));
}
} }

View File

@@ -0,0 +1,550 @@
/*
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 <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <exception>
#include <functional>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/regex.hpp>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_file_kw.h>
#include <ert/ecl/ecl_file_view.h>
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/util/ert_unique_ptr.hpp>
namespace StringUtils {
namespace {
std::string trim(const std::string& s)
{
const auto anchor_ws =
boost::regex(R"~~(^\s+([^\s]+)\s+$)~~");
auto m = boost::smatch{};
if (boost::regex_match(s, m, anchor_ws)) {
return m[1];
}
return s;
}
std::vector<std::string> split(const std::string& s)
{
if (s.empty()) {
// Single element vector whose only element is the empty
// string.
return { "" };
}
const auto sep = boost::regex(R"~~([\s,;.|]+)~~");
using TI = boost::sregex_token_iterator;
// vector<string>(begin, end)
//
// Range is every substring (i.e., token) in input string 's'
// that does NOT match 'sep'.
return{ TI(s.begin(), s.end(), sep, -1), TI{} };
}
} // namespace Anonymous
template <typename T>
struct StringTo;
template <>
struct StringTo<int>
{
static int value(const std::string& s);
};
template <>
struct StringTo<double>
{
static double value(const std::string& s);
};
template <>
struct StringTo<std::string>
{
static std::string value(const std::string& s);
};
int StringTo<int>::value(const std::string& s)
{
return std::stoi(s);
}
double StringTo<double>::value(const std::string& s)
{
return std::stod(s);
}
std::string StringTo<std::string>::value(const std::string& s)
{
return trim(s);
}
namespace VectorValue {
template <typename T>
std::vector<T> get(const std::string& s, std::true_type)
{
return split(s);
}
template <typename T>
std::vector<T> get(const std::string& s, std::false_type)
{
const auto tokens = split(s);
auto ret = std::vector<T>{};
ret.reserve(tokens.size());
for (const auto& token : tokens) {
ret.push_back(StringTo<T>::value(token));
}
return ret;
}
template <typename T>
std::vector<T> get(const std::string& s)
{
return get<T>(s, typename std::is_same<T, std::string>::type());
}
} // namespace VectorValue
} // namespace StringUtils
namespace {
struct PoreVolume
{
std::vector<double> data;
};
class VectorDifference
{
public:
using Vector = std::vector<double>;
using size_type = Vector::size_type;
VectorDifference(const Vector& x, const Vector& y)
: x_(x), y_(y)
{
if (x_.size() != y_.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< x_.size() << ", but got ("
<< x_.size() << ", " << y_.size() << ')';
throw std::domain_error(os.str());
}
}
size_type size() const
{
return x_.size();
}
bool empty() const
{
return this->size() == 0;
}
double operator[](const size_type i) const
{
return x_[i] - y_[i];
}
private:
const Vector& x_;
const Vector& y_;
};
template <class Vector1, class Vector2>
class VectorRatio
{
public:
using size_type = typename std::decay<
decltype(std::declval<Vector1>()[0])
>::type;
VectorRatio(const Vector1& x, const Vector2& y)
: x_(x), y_(y)
{
if (x_.size() != y.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< x_.size() << ", but got ("
<< x_.size() << ", " << y_.size() << ')';
throw std::domain_error(os.str());
}
}
size_type size() const
{
return x_.size();
}
bool empty() const
{
return x_.empty();
}
double operator[](const size_type i) const
{
return x_[i] / y_[i];
}
private:
const Vector1& x_;
const Vector2& y_;
};
struct ErrorMeasurement
{
double volume;
double inf;
};
struct ErrorTolerance
{
double absolute;
double relative;
};
struct AggregateErrors
{
std::vector<ErrorMeasurement> absolute;
std::vector<ErrorMeasurement> relative;
};
struct ReferenceToF
{
std::vector<double> forward;
std::vector<double> reverse;
};
template <class FieldVariable>
double volumeMetric(const PoreVolume& pv,
const FieldVariable& x)
{
if (x.size() != pv.data.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< pv.data.size() << ", but got ("
<< pv.data.size() << ", " << x.size() << ')';
throw std::domain_error(os.str());
}
auto num = 0.0;
auto den = 0.0;
for (decltype(pv.data.size())
i = 0, n = pv.data.size(); i < n; ++i)
{
num += std::abs(x[i]) * pv.data[i];
den += pv.data[i];
}
return num / den;
}
template <class FieldVariable>
double pointMetric(const FieldVariable& x)
{
static_assert(std::is_same<typename std::decay<decltype(std::abs(x[0]))>::type, double>::value,
"Field Variable Value Type Must be 'double'");
if (x.empty()) {
return 0;
}
auto max = 0*x[0] - 1;
for (decltype(x.size())
i = 0, n = x.size(); i < n; ++i)
{
const auto t = std::abs(x[i]);
if (t > max) {
max = t;
}
}
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::parameter::ParameterGroup& param)
{
const auto atol = param.getDefault("atol", 1.0e-8);
const auto rtol = param.getDefault("rtol", 5.0e-12);
return ErrorTolerance{ atol, rtol };
}
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;
}
ReferenceToF
loadReference(const ::Opm::parameter::ParameterGroup& param,
const int step,
const int nDigits)
{
namespace fs = boost::filesystem;
using VRef = std::reference_wrapper<std::vector<double>>;
auto fname = fs::path(param.get<std::string>("ref-dir"));
{
std::ostringstream os;
os << "tof-" << std::setw(nDigits) << std::setfill('0')
<< step << ".txt";
fname /= os.str();
}
fs::ifstream input(fname);
if (! input) {
std::ostringstream os;
os << "Unable to Open Reference Data File "
<< fname.filename();
throw std::domain_error(os.str());
}
auto tof = ReferenceToF{};
auto ref = std::array<VRef,2>{{ std::ref(tof.forward) ,
std::ref(tof.reverse) }};
{
auto i = static_cast<decltype(ref[0].get().size())>(0);
auto t = 0.0;
while (input >> t) {
ref[i].get().push_back(t);
i = (i + 1) % 2;
}
}
if (tof.forward.size() != tof.reverse.size()) {
std::ostringstream os;
os << "Unable to Read Consistent ToF Reference Data From "
<< fname.filename();
throw std::out_of_range(os.str());
}
return tof;
}
void computeErrors(const PoreVolume& pv,
const std::vector<double>& ref,
const ::Opm::FlowDiagnostics::Solution& fd,
AggregateErrors& E)
{
const auto tof = fd.timeOfFlight();
const auto diff = VectorDifference(tof, ref); // tof - ref
using Vector1 = std::decay<decltype(diff)>::type;
using Vector2 = std::decay<decltype(ref)>::type;
using Ratio = VectorRatio<Vector1, Vector2>;
const auto rat = Ratio(diff, ref); // (tof - ref) / ref
auto abs = ErrorMeasurement{};
{
abs.volume = volumeMetric(pv, diff);
abs.inf = pointMetric ( diff);
}
auto rel = ErrorMeasurement{};
{
rel.volume = volumeMetric(pv, rat);
rel.inf = pointMetric ( rat);
}
E.absolute.push_back(std::move(abs));
E.relative.push_back(std::move(rel));
}
std::array<AggregateErrors, 2>
sampleDifferences(example::Setup&& setup,
const std::vector<int>& steps)
{
const auto start =
std::vector<Opm::FlowDiagnostics::CellSet>{};
const auto nDigits = numDigits(steps);
const auto pv = PoreVolume{ setup.graph.poreVolume() };
auto E = std::array<AggregateErrors, 2>{};
for (const auto& step : steps) {
if (step == 0) {
// Ignore initial condition
continue;
}
if (! setup.selectReportStep(step)) {
continue;
}
const auto ref = loadReference(setup.param, step, nDigits);
{
const auto fwd = setup.toolbox
.computeInjectionDiagnostics(start);
computeErrors(pv, ref.forward, fwd.fd, E[0]);
}
{
const auto rev = setup.toolbox
.computeProductionDiagnostics(start);
computeErrors(pv, ref.reverse, rev.fd, E[1]);
}
}
return E;
}
bool errorAcceptable(const std::vector<ErrorMeasurement>& E,
const double tol)
{
return std::accumulate(std::begin(E), std::end(E), true,
[tol](const bool ok, const ErrorMeasurement& e)
{
// Fine if at least one of .volume or .inf <= tol.
return ok && ! ((e.volume > tol) && (e.inf > tol));
});
}
bool everythingFine(const AggregateErrors& E,
const ErrorTolerance& tol)
{
return errorAcceptable(E.absolute, tol.absolute)
&& errorAcceptable(E.relative, tol.relative);
}
} // namespace Anonymous
int main(int argc, char* argv[])
try {
auto setup = example::Setup(argc, argv);
const auto tol = testTolerances(setup.param);
const auto steps = availableReportSteps(setup.file_paths);
const auto E = sampleDifferences(std::move(setup), steps);
const auto ok =
everythingFine(E[0], tol) && everythingFine(E[1], tol);
std::cout << (ok ? "OK" : "FAIL") << '\n';
if (! ok) {
return EXIT_FAILURE;
}
}
catch (const std::exception& e) {
std::cerr << "Caught Exception: " << e.what() << '\n';
return EXIT_FAILURE;
}

View File

@@ -0,0 +1,587 @@
/*
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 <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <exception>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/regex.hpp>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_file_kw.h>
#include <ert/ecl/ecl_file_view.h>
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/util/ert_unique_ptr.hpp>
namespace StringUtils {
namespace {
std::string trim(const std::string& s)
{
const auto anchor_ws =
boost::regex(R"~~(^\s+([^\s]+)\s+$)~~");
auto m = boost::smatch{};
if (boost::regex_match(s, m, anchor_ws)) {
return m[1];
}
return s;
}
std::vector<std::string> split(const std::string& s)
{
if (s.empty()) {
// Single element vector whose only element is the empty
// string.
return { "" };
}
const auto sep = boost::regex(R"~~([\s,;.|]+)~~");
using TI = boost::sregex_token_iterator;
// vector<string>(begin, end)
//
// Range is every substring (i.e., token) in input string 's'
// that does NOT match 'sep'.
return{ TI(s.begin(), s.end(), sep, -1), TI{} };
}
} // namespace Anonymous
template <typename T>
struct StringTo;
template <>
struct StringTo<int>
{
static int value(const std::string& s);
};
template <>
struct StringTo<double>
{
static double value(const std::string& s);
};
template <>
struct StringTo<std::string>
{
static std::string value(const std::string& s);
};
int StringTo<int>::value(const std::string& s)
{
return std::stoi(s);
}
double StringTo<double>::value(const std::string& s)
{
return std::stod(s);
}
std::string StringTo<std::string>::value(const std::string& s)
{
return trim(s);
}
namespace VectorValue {
template <typename T>
std::vector<T> get(const std::string& s, std::true_type)
{
return split(s);
}
template <typename T>
std::vector<T> get(const std::string& s, std::false_type)
{
const auto tokens = split(s);
auto ret = std::vector<T>{};
ret.reserve(tokens.size());
for (const auto& token : tokens) {
ret.push_back(StringTo<T>::value(token));
}
return ret;
}
template <typename T>
std::vector<T> get(const std::string& s)
{
return get<T>(s, typename std::is_same<T, std::string>::type());
}
} // namespace VectorValue
} // namespace StringUtils
namespace {
struct Reference
{
std::vector<double> data;
};
struct Calculated
{
std::vector<double> data;
};
class VectorUnits
{
private:
using USys = ::Opm::ECLUnits::UnitSystem;
public:
using UnitConvention = ::Opm::ECLGraph::UnitConvention;
VectorUnits()
: units_({ { "pressure", &USys::pressure } })
{
}
UnitConvention getUnit(const std::string& vector) const
{
auto p = units_.find(vector);
if (p == units_.end()) {
std::ostringstream os;
os << "Unsupported Vector Quantity '" << vector << '\'';
throw std::domain_error(os.str());
}
return p->second;
}
private:
std::map<std::string, UnitConvention> units_;
};
class VectorDifference
{
public:
using Vector = std::vector<double>;
using size_type = Vector::size_type;
VectorDifference(const Vector& x, const Vector& y)
: x_(x), y_(y)
{
if (x_.size() != y_.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< x_.size() << ", but got ("
<< x_.size() << ", " << y_.size() << ')';
throw std::domain_error(os.str());
}
}
size_type size() const
{
return x_.size();
}
bool empty() const
{
return this->size() == 0;
}
double operator[](const size_type i) const
{
return x_[i] - y_[i];
}
private:
const Vector& x_;
const Vector& y_;
};
template <class Vector1, class Vector2>
class VectorRatio
{
public:
using size_type = typename std::decay<
decltype(std::declval<Vector1>()[0])
>::type;
VectorRatio(const Vector1& x, const Vector2& y)
: x_(x), y_(y)
{
if (x_.size() != y.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< x_.size() << ", but got ("
<< x_.size() << ", " << y_.size() << ')';
throw std::domain_error(os.str());
}
}
size_type size() const
{
return x_.size();
}
bool empty() const
{
return x_.empty();
}
double operator[](const size_type i) const
{
return x_[i] / y_[i];
}
private:
const Vector1& x_;
const Vector2& y_;
};
struct ErrorMeasurement
{
double volume;
double inf;
};
struct ErrorTolerance
{
double absolute;
double relative;
};
struct AggregateErrors
{
std::vector<ErrorMeasurement> absolute;
std::vector<ErrorMeasurement> relative;
};
struct ReferenceSolution
{
std::vector<double> raw;
std::vector<double> SI;
};
template <class FieldVariable>
double volumeMetric(const FieldVariable& x)
{
auto result = 0.0;
for (decltype(x.size())
i = 0, n = x.size(); i < n; ++i)
{
const auto m = std::abs(x[i]);
result += m * m;
}
return std::sqrt(result / x.size());
}
template <class FieldVariable>
double pointMetric(const FieldVariable& x)
{
static_assert(std::is_same<typename std::decay<decltype(std::abs(x[0]))>::type, double>::value,
"Field Variable Value Type Must be 'double'");
if (x.empty()) {
return 0;
}
auto max = 0*x[0] - 1;
for (decltype(x.size())
i = 0, n = x.size(); i < n; ++i)
{
const auto t = std::abs(x[i]);
if (t > max) {
max = t;
}
}
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::parameter::ParameterGroup& param)
{
const auto atol = param.getDefault("atol", 1.0e-8);
const auto rtol = param.getDefault("rtol", 5.0e-12);
return ErrorTolerance{ atol, rtol };
}
std::vector<std::string>
testQuantities(const ::Opm::parameter::ParameterGroup& param)
{
return StringUtils::VectorValue::
get<std::string>(param.get<std::string>("quant"));
}
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;
}
ReferenceSolution
loadReference(const ::Opm::parameter::ParameterGroup& param,
const std::string& quant,
const int step,
const int nDigits)
{
namespace fs = boost::filesystem;
using VRef = std::reference_wrapper<std::vector<double>>;
auto x = ReferenceSolution{};
auto ref = std::array<VRef,2>{{ std::ref(x.raw) ,
std::ref(x.SI ) }};
auto i = 0;
for (const auto* q : { "raw", "SI" }) {
auto fname = fs::path(param.get<std::string>("ref-dir"))
/ boost::algorithm::to_lower_copy(quant);
{
std::ostringstream os;
os << q << '-'
<< std::setw(nDigits) << std::setfill('0')
<< step << ".txt";
fname /= os.str();
}
fs::ifstream input(fname);
if (input) {
ref[i].get().assign(std::istream_iterator<double>(input),
std::istream_iterator<double>());
}
i += 1;
}
if (x.raw.size() != x.SI.size()) {
std::ostringstream os;
os << "Unable to Read Consistent Reference Data From '"
<< param.get<std::string>("ref-dir") << "' In Step "
<< step;
throw std::out_of_range(os.str());
}
return x;
}
void computeErrors(const Reference& ref,
const Calculated& calc,
AggregateErrors& E)
{
const auto diff =
VectorDifference(calc.data, ref.data); // calc - ref
using Vector1 = std::decay<decltype(diff)>::type;
using Vector2 = std::decay<decltype(ref.data)>::type;
using Ratio = VectorRatio<Vector1, Vector2>;
const auto rat = Ratio(diff, ref.data); // (tof - ref) / ref
auto abs = ErrorMeasurement{};
{
abs.volume = volumeMetric(diff);
abs.inf = pointMetric (diff);
}
auto rel = ErrorMeasurement{};
{
rel.volume = volumeMetric(rat);
rel.inf = pointMetric (rat);
}
E.absolute.push_back(std::move(abs));
E.relative.push_back(std::move(rel));
}
std::array<AggregateErrors, 2>
sampleDifferences(const ::Opm::ECLGraph& graph,
const ::Opm::parameter::ParameterGroup& param,
const std::string& quant,
const std::vector<int>& steps)
{
const auto ECLquant = boost::algorithm::to_upper_copy(quant);
auto unit = VectorUnits()
.getUnit(boost::algorithm::to_lower_copy(quant));
const auto start =
std::vector<Opm::FlowDiagnostics::CellSet>{};
const auto nDigits = numDigits(steps);
auto E = std::array<AggregateErrors, 2>{};
for (const auto& step : steps) {
if (! graph.selectReportStep(step)) {
continue;
}
const auto ref = loadReference(param, quant, step, nDigits);
{
const auto raw = Calculated {
graph.rawLinearisedCellData<double>(ECLquant)
};
computeErrors(Reference{ ref.raw }, raw, E[0]);
}
{
const auto SI = Calculated {
graph.linearisedCellData(ECLquant, unit)
};
computeErrors(Reference{ ref.SI }, SI, E[1]);
}
}
return E;
}
bool errorAcceptable(const std::vector<ErrorMeasurement>& E,
const double tol)
{
return std::accumulate(std::begin(E), std::end(E), true,
[tol](const bool ok, const ErrorMeasurement& e)
{
// Fine if at least one of .volume or .inf <= tol.
return ok && ! ((e.volume > tol) && (e.inf > tol));
});
}
bool everythingFine(const AggregateErrors& E,
const ErrorTolerance& tol)
{
return errorAcceptable(E.absolute, tol.absolute)
&& errorAcceptable(E.relative, tol.relative);
}
} // 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 steps = availableReportSteps(pth);
const auto graph = example::initGraph(pth);
auto all_ok = true;
for (const auto& quant : testQuantities(prm)) {
const auto E = sampleDifferences(graph, prm, quant, steps);
const auto ok =
everythingFine(E[0], tol) && everythingFine(E[1], tol);
std::cout << quant << ": " << (ok ? "OK" : "FAIL") << '\n';
all_ok = all_ok && ok;
}
if (! all_ok) {
return EXIT_FAILURE;
}
}
catch (const std::exception& e) {
std::cerr << "Caught Exception: " << e.what() << '\n';
return EXIT_FAILURE;
}

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 <opm/utility/ECLGraph.hpp>
#include <examples/exampleSetup.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <exception>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
namespace {
class VectorDifference
{
public:
using Vector = std::vector<double>;
using size_type = Vector::size_type;
VectorDifference(const Vector& x, const Vector& y)
: x_(x), y_(y)
{
if (x_.size() != y_.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< x_.size() << ", but got ("
<< x_.size() << ", " << y_.size() << ')';
throw std::domain_error(os.str());
}
}
size_type size() const
{
return x_.size();
}
bool empty() const
{
return this->size() == 0;
}
double operator[](const size_type i) const
{
return x_[i] - y_[i];
}
private:
const Vector& x_;
const Vector& y_;
};
template <class Vector1, class Vector2>
class VectorRatio
{
public:
using size_type = typename std::decay<
decltype(std::declval<Vector1>()[0])
>::type;
VectorRatio(const Vector1& x, const Vector2& y)
: x_(x), y_(y)
{
if (x_.size() != y.size()) {
std::ostringstream os;
os << "Incompatible Array Sizes: Expected 2x"
<< x_.size() << ", but got ("
<< x_.size() << ", " << y_.size() << ')';
throw std::domain_error(os.str());
}
}
size_type size() const
{
return x_.size();
}
bool empty() const
{
return x_.empty();
}
double operator[](const size_type i) const
{
return x_[i] / y_[i];
}
private:
const Vector1& x_;
const Vector2& y_;
};
struct ErrorTolerance
{
double absolute;
double relative;
};
template <class FieldVariable>
double pointMetric(const FieldVariable& x)
{
static_assert(std::is_same<typename std::decay<decltype(std::abs(x[0]))>::type, double>::value,
"Field Variable Value Type Must be 'double'");
if (x.empty()) {
return 0;
}
auto max = 0*x[0] - 1;
for (decltype(x.size())
i = 0, n = x.size(); i < n; ++i)
{
const auto t = std::abs(x[i]);
if (t > max) {
max = t;
}
}
return max;
}
ErrorTolerance
testTolerances(const ::Opm::parameter::ParameterGroup& param)
{
const auto atol = param.getDefault("atol", 1.0e-8);
const auto rtol = param.getDefault("rtol", 5.0e-12);
return ErrorTolerance{ atol, rtol };
}
std::vector<double>
loadReference(const ::Opm::parameter::ParameterGroup& param)
{
namespace fs = boost::filesystem;
const auto fname =
fs::path(param.get<std::string>("ref-dir")) / "trans.txt";
fs::ifstream input(fname);
if (! input) {
std::ostringstream os;
os << "Unable to Open Reference Trans-Data File "
<< fname.filename();
throw std::domain_error(os.str());
}
return {
std::istream_iterator<double>(input),
std::istream_iterator<double>()
};
}
bool transfieldAcceptable(const ::Opm::parameter::ParameterGroup& param,
const std::vector<double>& trans)
{
const auto Tref = loadReference(param);
if (Tref.size() != trans.size()) {
return false;
}
const auto diff = VectorDifference{ trans, Tref };
using Vector1 = std::decay<decltype(diff)>::type;
using Vector2 = std::decay<decltype(Tref)>::type;
using Ratio = VectorRatio<Vector1, Vector2>;
const auto rat = Ratio(diff, Tref);
const auto tol = testTolerances(param);
return ! ((pointMetric(diff) > tol.absolute) ||
(pointMetric(rat) > tol.relative));
}
} // 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 = example::initGraph(pth);
const auto T = G.transmissibility();
const auto ok = transfieldAcceptable(prm, T);
std::cout << (ok ? "OK" : "FAIL") << '\n';
if (! ok) {
return EXIT_FAILURE;
}
}
catch (const std::exception& e) {
std::cerr << "Caught Exception: " << e.what() << '\n';
return EXIT_FAILURE;
}

View File

@@ -0,0 +1,235 @@
/*
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_ASSEMBLED_CONNECTIONS
#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/ECLUnitHandling.hpp>
#include <exception>
#include <stdexcept>
BOOST_AUTO_TEST_SUITE (Basic_Conversion)
BOOST_AUTO_TEST_CASE (Constructor)
{
auto M = ::Opm::ECLUnits::createUnitSystem(1); // METRIC
auto F = ::Opm::ECLUnits::createUnitSystem(2); // FIELD
auto L = ::Opm::ECLUnits::createUnitSystem(3); // LAB
auto P = ::Opm::ECLUnits::createUnitSystem(4); // PVT-M
BOOST_CHECK_THROW(::Opm::ECLUnits::createUnitSystem( 5), std::runtime_error);
BOOST_CHECK_THROW(::Opm::ECLUnits::createUnitSystem(-1), std::runtime_error);
}
BOOST_AUTO_TEST_CASE (Metric)
{
auto M = ::Opm::ECLUnits::createUnitSystem(1);
// Pressure (bars)
{
const auto scale = M->pressure();
const auto expect = 100.0e3;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume Rate (rm^3 / day)
{
const auto scale = M->reservoirRate();
const auto expect = 1.157407407407407e-05;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume (rm^3)
{
const auto scale = M->reservoirVolume();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (day)
{
const auto scale = M->time();
const auto expect = 86.400e+03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Transmissibility ((cP * m^3) / (day * barsa))
{
const auto scale = M->transmissibility();
const auto expect = 1.157407407407407e-13;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (Field)
{
auto F = ::Opm::ECLUnits::createUnitSystem(2);
// Pressure (psi)
{
const auto scale = F->pressure();
const auto expect = 6.894757293168360e+03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume Rate (rb / day)
{
const auto scale = F->reservoirRate();
const auto expect = 1.840130728333334e-06;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume (rb)
{
const auto scale = F->reservoirVolume();
const auto expect = 1.589872949280001e-01;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (day)
{
const auto scale = F->time();
const auto expect = 86.400e+03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Transmissibility ((cP * rb) / (day * psia))
{
const auto scale = F->transmissibility();
const auto expect = 2.668883979653090e-13;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (Lab)
{
auto L = ::Opm::ECLUnits::createUnitSystem(3);
// Pressure (atm)
{
const auto scale = L->pressure();
const auto expect = 101.325e+03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume Rate (r(cm)^3 / h)
{
const auto scale = L->reservoirRate();
const auto expect = 2.777777777777778e-10;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume (r(cm)^3)
{
const auto scale = L->reservoirVolume();
const auto expect = 1.0e-06;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (hour)
{
const auto scale = L->time();
const auto expect = 3600.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Transmissibility ((cP * (cm)^3) / (h * atm))
{
const auto scale = L->transmissibility();
const auto expect = 2.741453518655592e-18;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_CASE (PVT_M)
{
auto P = ::Opm::ECLUnits::createUnitSystem(4);
// Pressure (atm)
{
const auto scale = P->pressure();
const auto expect = 101.325e+03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume Rate (rm^3 / day)
{
const auto scale = P->reservoirRate();
const auto expect = 1.157407407407407e-05;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Reservoir Volume (rm^3)
{
const auto scale = P->reservoirVolume();
const auto expect = 1.0;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Time (day)
{
const auto scale = P->time();
const auto expect = 86.400e+03;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
// Transmissibility ((cP * rm^3 / (day * atm))
{
const auto scale = P->transmissibility();
const auto expect = 1.142272299439830e-13;
BOOST_CHECK_CLOSE(scale, expect, 1.0e-10);
}
}
BOOST_AUTO_TEST_SUITE_END ()

View File

@@ -13,47 +13,60 @@
//=========================================================================== //===========================================================================
/* /*
Copyright 2009, 2010, 2011, 2012 SINTEF ICT, Applied Mathematics. Copyright 2009, 2010, 2011, 2012 SINTEF ICT, Applied Mathematics.
Copyright 2009, 2010, 2011, 2012 Statoil ASA. Copyright 2009, 2010, 2011, 2012 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify This file is part of the Open Porous Media project (OPM).
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or OPM is free software: you can redistribute it and/or modify
(at your option) any later version. it under the terms of the GNU General Public License as published by
OPM is distributed in the hope that it will be useful, the Free Software Foundation, either version 3 of the License, or
but WITHOUT ANY WARRANTY; without even the implied warranty of (at your option) any later version.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. OPM is distributed in the hope that it will be useful,
You should have received a copy of the GNU General Public License but WITHOUT ANY WARRANTY; without even the implied warranty of
along with OPM. If not, see <http://www.gnu.org/licenses/>. 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_UNITS_HEADER #ifndef OPM_UNITS_HEADER
#define OPM_UNITS_HEADER #define OPM_UNITS_HEADER
/** /**
* \file The unit sets emplyed in ECLIPSE, in particular the FIELD units,
* Constants and routines to assist in handling units of measurement. These are are quite inconsistent. Ideally one should choose units for a set
* geared towards handling common units in reservoir descriptions. of base quantities like Mass,Time and Length and then derive the
*/ units for e.g. pressure and flowrate in a consisten manner. However
that is not the case; for instance in the metric system we have:
namespace Opm [Length] = meters
{ [time] = days
namespace prefix [mass] = kg
This should give:
[Pressure] = [mass] / ([length] * [time]^2) = kg / (m * days * days)
Instead pressure is given in Bars. When it comes to FIELD units the
number of such examples is long.
*/
namespace Opm {
namespace prefix
/// Conversion prefix for units. /// Conversion prefix for units.
{ {
const double micro = 1.0e-6; /**< Unit prefix [\f$\mu\f$] */ constexpr const double micro = 1.0e-6; /**< Unit prefix [\f$\mu\f$] */
const double milli = 1.0e-3; /**< Unit prefix [m] */ constexpr const double milli = 1.0e-3; /**< Unit prefix [m] */
const double centi = 1.0e-2; /**< Non-standard unit prefix [c] */ constexpr const double centi = 1.0e-2; /**< Non-standard unit prefix [c] */
const double deci = 1.0e-1; /**< Non-standard unit prefix [d] */ constexpr const double deci = 1.0e-1; /**< Non-standard unit prefix [d] */
const double kilo = 1.0e3; /**< Unit prefix [k] */ constexpr const double kilo = 1.0e3; /**< Unit prefix [k] */
const double mega = 1.0e6; /**< Unit prefix [M] */ constexpr const double mega = 1.0e6; /**< Unit prefix [M] */
const double giga = 1.0e9; /**< Unit prefix [G] */ constexpr const double giga = 1.0e9; /**< Unit prefix [G] */
} // namespace prefix } // namespace prefix
namespace unit namespace unit
/// Definition of various units. /// Definition of various units.
/// All the units are defined in terms of international standard /// All the units are defined in terms of international standard
/// units (SI). Example of use: We define a variable \c k which /// units (SI). Example of use: We define a variable \c k which
@@ -68,162 +81,246 @@ namespace Opm
/// using namespace Opm::prefix /// using namespace Opm::prefix
/// double k = 1.0*milli*darcy; /// double k = 1.0*milli*darcy;
/// \endcode /// \endcode
{ {
///\name Common powers ///\name Common powers
/// @{ /// @{
inline double square(double v) { return v * v; } constexpr double square(double v) { return v * v; }
inline double cubic (double v) { return v * v * v; } constexpr double cubic (double v) { return v * v * v; }
/// @} /// @}
// -------------------------------------------------------------- // --------------------------------------------------------------
// Basic (fundamental) units and conversions // Basic (fundamental) units and conversions
// -------------------------------------------------------------- // --------------------------------------------------------------
/// \name Length /// \name Length
/// @{ /// @{
const double meter = 1; constexpr const double meter = 1;
const double inch = 2.54 * prefix::centi*meter; constexpr const double inch = 2.54 * prefix::centi*meter;
const double feet = 12 * inch; constexpr const double feet = 12 * inch;
/// @} /// @}
/// \name Time /// \name Time
/// @{ /// @{
const double second = 1; constexpr const double second = 1;
const double minute = 60 * second; constexpr const double minute = 60 * second;
const double hour = 60 * minute; constexpr const double hour = 60 * minute;
const double day = 24 * hour; constexpr const double day = 24 * hour;
const double year = 365 * day; constexpr const double year = 365 * day;
/// @} /// @}
/// \name Volume /// \name Volume
/// @{ /// @{
const double gallon = 231 * cubic(inch); constexpr const double gallon = 231 * cubic(inch);
const double stb = 42 * gallon; constexpr const double stb = 42 * gallon;
const double liter = 1 * cubic(prefix::deci*meter); constexpr const double liter = 1 * cubic(prefix::deci*meter);
/// @} /// @}
/// \name Mass /// \name Mass
/// @{ /// @{
const double kilogram = 1; constexpr const double kilogram = 1;
// http://en.wikipedia.org/wiki/Pound_(mass)#Avoirdupois_pound constexpr const double gram = 1.0e-3 * kilogram;
const double pound = 0.45359237 * kilogram; // http://en.wikipedia.org/wiki/Pound_(mass)#Avoirdupois_pound
/// @} constexpr const double pound = 0.45359237 * kilogram;
/// @}
// -------------------------------------------------------------- // --------------------------------------------------------------
// Standardised constants // Standardised constants
// -------------------------------------------------------------- // --------------------------------------------------------------
/// \name Standardised constant /// \name Standardised constant
/// @{ /// @{
const double gravity = 9.80665 * meter/square(second); constexpr const double gravity = 9.80665 * meter/square(second);
/// @} /// @}
// -------------------------------------------------------------- // --------------------------------------------------------------
// Derived units and conversions // Derived units and conversions
// -------------------------------------------------------------- // --------------------------------------------------------------
/// \name Force /// \name Force
/// @{ /// @{
const double Newton = kilogram*meter / square(second); // == 1 constexpr const double Newton = kilogram*meter / square(second); // == 1
const double lbf = pound * gravity; // Pound-force constexpr const double dyne = 1e-5*Newton;
constexpr const double lbf = pound * gravity; // Pound-force
/// @} /// @}
/// \name Pressure /// \name Pressure
/// @{ /// @{
const double Pascal = Newton / square(meter); // == 1 constexpr const double Pascal = Newton / square(meter); // == 1
const double barsa = 100000 * Pascal; constexpr const double barsa = 100000 * Pascal;
const double atm = 101325 * Pascal; constexpr const double atm = 101325 * Pascal;
const double psia = lbf / square(inch); constexpr const double psia = lbf / square(inch);
/// @}
/// \name Temperature. This one is more complicated
/// because the unit systems used by Eclipse (i.e. degrees
/// Celsius and degrees Fahrenheit require to add or
/// subtract an offset for the conversion between from/to
/// Kelvin
/// @{
constexpr const double degCelsius = 1.0; // scaling factor <20>C -> K
constexpr const double degCelsiusOffset = 273.15; // offset for the <20>C -> K conversion
constexpr const double degFahrenheit = 5.0/9; // scaling factor <20>F -> K
constexpr const double degFahrenheitOffset = 255.37; // offset for the <20>C -> K conversion
/// @} /// @}
/// \name Viscosity /// \name Viscosity
/// @{ /// @{
const double Pas = Pascal * second; // == 1 constexpr const double Pas = Pascal * second; // == 1
const double Poise = prefix::deci*Pas; constexpr const double Poise = prefix::deci*Pas;
/// @} /// @}
namespace perm_details { namespace perm_details {
const double p_grad = atm / (prefix::centi*meter); constexpr const double p_grad = atm / (prefix::centi*meter);
const double area = square(prefix::centi*meter); constexpr const double area = square(prefix::centi*meter);
const double flux = cubic (prefix::centi*meter) / second; constexpr const double flux = cubic (prefix::centi*meter) / second;
const double velocity = flux / area; constexpr const double velocity = flux / area;
const double visc = prefix::centi*Poise; constexpr const double visc = prefix::centi*Poise;
const double darcy = (velocity * visc) / p_grad; constexpr const double darcy = (velocity * visc) / p_grad;
// == 1e-7 [m^2] / 101325 // == 1e-7 [m^2] / 101325
// == 9.869232667160130e-13 [m^2] // == 9.869232667160130e-13 [m^2]
} }
/// \name Permeability /// \name Permeability
/// @{ /// @{
/// ///
/// A porous medium with a permeability of 1 darcy permits a flow (flux) /// A porous medium with a permeability of 1 darcy permits a flow (flux)
/// of \f$1\,\mathit{cm}^3/s\f$ of a fluid with viscosity /// of \f$1\,\mathit{cm}^3/s\f$ of a fluid with viscosity
/// \f$1\,\mathit{cP}\f$ (\f$1\,mPa\cdot s\f$) under a pressure gradient /// \f$1\,\mathit{cP}\f$ (\f$1\,mPa\cdot s\f$) under a pressure gradient
/// of \f$1\,\mathit{atm}/\mathit{cm}\f$ acting across an area of /// of \f$1\,\mathit{atm}/\mathit{cm}\f$ acting across an area of
/// \f$1\,\mathit{cm}^2\f$. /// \f$1\,\mathit{cm}^2\f$.
/// ///
const double darcy = perm_details::darcy; constexpr const double darcy = perm_details::darcy;
/// @} /// @}
/** /**
* Unit conversion routines. * Unit conversion routines.
*/ */
namespace convert { namespace convert {
/** /**
* Convert from external units of measurements to equivalent * Convert from external units of measurements to equivalent
* internal units of measurements. Note: The internal units of * internal units of measurements. Note: The internal units of
* measurements are *ALWAYS*, and exclusively, SI. * measurements are *ALWAYS*, and exclusively, SI.
* *
* Example: Convert a double @c kx, containing a permeability value * Example: Convert a double @c kx, containing a permeability value
* in units of milli-darcy (mD) to the equivalent value in SI units * in units of milli-darcy (mD) to the equivalent value in SI units
* (i.e., \f$m^2\f$). * (i.e., \f$m^2\f$).
* \code * \code
* using namespace Opm::unit; * using namespace Opm::unit;
* using namespace Opm::prefix; * using namespace Opm::prefix;
* convert::from(kx, milli*darcy); * convert::from(kx, milli*darcy);
* \endcode * \endcode
* *
* @param[in] q Physical quantity. * @param[in] q Physical quantity.
* @param[in] unit Physical unit of measurement. * @param[in] unit Physical unit of measurement.
* @return Value of @c q in equivalent SI units of measurements. * @return Value of @c q in equivalent SI units of measurements.
*/ */
inline double from(const double q, const double unit) constexpr double from(const double q, const double unit)
{ {
return q * unit; return q * unit;
} }
/** /**
* Convert from internal units of measurements to equivalent * Convert from internal units of measurements to equivalent
* external units of measurements. Note: The internal units of * external units of measurements. Note: The internal units of
* measurements are *ALWAYS*, and exclusively, SI. * measurements are *ALWAYS*, and exclusively, SI.
* *
* Example: Convert a <CODE>std::vector<double> p</CODE>, containing * Example: Convert a <CODE>std::vector<double> p</CODE>, containing
* pressure values in the SI unit Pascal (i.e., unit::Pascal) to the * pressure values in the SI unit Pascal (i.e., unit::Pascal) to the
* equivalent values in Psi (unit::psia). * equivalent values in Psi (unit::psia).
* \code * \code
* using namespace Opm::unit; * using namespace Opm::unit;
* std::transform(p.begin(), p.end(), p.begin(), * std::transform(p.begin(), p.end(), p.begin(),
* boost::bind(convert::to, _1, psia)); * boost::bind(convert::to, _1, psia));
* \endcode * \endcode
* *
* @param[in] q Physical quantity, measured in SI units. * @param[in] q Physical quantity, measured in SI units.
* @param[in] unit Physical unit of measurement. * @param[in] unit Physical unit of measurement.
* @return Value of @c q in unit <CODE>unit</CODE>. * @return Value of @c q in unit <CODE>unit</CODE>.
*/ */
inline double to(const double q, const double unit) constexpr double to(const double q, const double unit)
{ {
return q / unit; return q / unit;
} }
} // namespace convert } // namespace convert
}
namespace Metric {
using namespace prefix;
using namespace unit;
constexpr const double Pressure = barsa;
constexpr const double Temperature = degCelsius;
constexpr const double TemperatureOffset = degCelsiusOffset;
constexpr const double AbsoluteTemperature = degCelsius; // actually [K], but the these two are identical
constexpr const double Length = meter;
constexpr const double Time = day;
constexpr const double Mass = kilogram;
constexpr const double Permeability = milli*darcy;
constexpr const double Transmissibility = centi*Poise*cubic(meter)/(day*barsa);
constexpr const double LiquidSurfaceVolume = cubic(meter);
constexpr const double GasSurfaceVolume = cubic(meter);
constexpr const double ReservoirVolume = cubic(meter);
constexpr const double GasDissolutionFactor = GasSurfaceVolume/LiquidSurfaceVolume;
constexpr const double OilDissolutionFactor = LiquidSurfaceVolume/GasSurfaceVolume;
constexpr const double Density = kilogram/cubic(meter);
constexpr const double PolymerDensity = kilogram/cubic(meter);
constexpr const double Salinity = kilogram/cubic(meter);
constexpr const double Viscosity = centi*Poise;
constexpr const double Timestep = day;
constexpr const double SurfaceTension = dyne/(centi*meter);
}
#ifndef HAS_ATTRIBUTE_UNUSED namespace Field {
namespace detail { using namespace prefix;
// Some units are sometimes unused, and generate a (potentially) large number of warnings using namespace unit;
// Adding them here silences these warnings, and should have no side-effects constexpr const double Pressure = psia;
//JJS double __attribute__((unused)) unused_units = stb + liter + barsa + psia + darcy; constexpr const double Temperature = degFahrenheit;
} // namespace detail constexpr const double TemperatureOffset = degFahrenheitOffset;
#endif constexpr const double AbsoluteTemperature = degFahrenheit; // actually [<5B>R], but the these two are identical
constexpr const double Length = feet;
constexpr const double Time = day;
constexpr const double Mass = pound;
constexpr const double Permeability = milli*darcy;
constexpr const double Transmissibility = centi*Poise*stb/(day*psia);
constexpr const double LiquidSurfaceVolume = stb;
constexpr const double GasSurfaceVolume = 1000*cubic(feet);
constexpr const double ReservoirVolume = stb;
constexpr const double GasDissolutionFactor = GasSurfaceVolume/LiquidSurfaceVolume;
constexpr const double OilDissolutionFactor = LiquidSurfaceVolume/GasSurfaceVolume;
constexpr const double Density = pound/cubic(feet);
constexpr const double PolymerDensity = pound/stb;
constexpr const double Salinity = pound/stb;
constexpr const double Viscosity = centi*Poise;
constexpr const double Timestep = day;
constexpr const double SurfaceTension = dyne/(centi*meter);
}
namespace Lab {
using namespace prefix;
using namespace unit;
constexpr const double Pressure = atm;
constexpr const double Temperature = degCelsius;
constexpr const double TemperatureOffset = degCelsiusOffset;
constexpr const double AbsoluteTemperature = degCelsius; // actually [K], but the these two are identical
constexpr const double Length = centi*meter;
constexpr const double Time = hour;
constexpr const double Mass = gram;
constexpr const double Permeability = milli*darcy;
constexpr const double Transmissibility = centi*Poise*cubic(centi*meter)/(hour*atm);
constexpr const double LiquidSurfaceVolume = cubic(centi*meter);
constexpr const double GasSurfaceVolume = cubic(centi*meter);
constexpr const double ReservoirVolume = cubic(centi*meter);
constexpr const double GasDissolutionFactor = GasSurfaceVolume/LiquidSurfaceVolume;
constexpr const double OilDissolutionFactor = LiquidSurfaceVolume/GasSurfaceVolume;
constexpr const double Density = gram/cubic(centi*meter);
constexpr const double PolymerDensity = gram/cubic(centi*meter);
constexpr const double Salinity = gram/cubic(centi*meter);
constexpr const double Viscosity = centi*Poise;
constexpr const double Timestep = hour;
constexpr const double SurfaceTension = dyne/(centi*meter);
}
}
} // namespace unit
} // namespace Opm
#endif // OPM_UNITS_HEADER #endif // OPM_UNITS_HEADER

View File

@@ -323,18 +323,14 @@ namespace FlowDiagnostics
// Compute time-of-flight and tracer. // Compute time-of-flight and tracer.
tracer_[cell] = is_start_[cell] ? 1.0 : upwind_tracer_contrib / total_influx; tracer_[cell] = is_start_[cell] ? 1.0 : upwind_tracer_contrib / total_influx;
//tof_[cell] = (pv_[cell]*tracer_[cell] + upwind_tof_contrib) / (total_influx*tracer_[cell]);
if ( tracer_[cell] > 0.0 ) if (tracer_[cell] > 0.0) {
{
tof_[cell] = (pv_[cell]*tracer_[cell] + upwind_tof_contrib) tof_[cell] = (pv_[cell]*tracer_[cell] + upwind_tof_contrib)
/ (total_influx * tracer_[cell]); / (total_influx * tracer_[cell]);
} }
else else {
{
tof_[cell] = max_tof_; tof_[cell] = max_tof_;
} }
} }