#1372 Update flow diagnostics and flow diagnostics applications

This commit is contained in:
Magne Sjaastad 2017-05-12 12:17:11 +02:00
parent 4204d49f30
commit 3c07eafab2
25 changed files with 1674 additions and 530 deletions

View File

@ -11,10 +11,10 @@ set(NRLIB_GITHUB_SHA "ba35d4359882f1c6f5e9dc30eb95fe52af50fd6f")
set(ERT_GITHUB_SHA "06a39878636af0bc52582430ad0431450e51139c")
# https://github.com/OPM/opm-flowdiagnostics
set(OPM_FLOWDIAGNOSTICS_SHA "a14dc4ba1302bcc1e0aeb35c5de6b4bd39bce98")
set(OPM_FLOWDIAGNOSTICS_SHA "2c5fb55db4c4ded49c14161dd16463e1207da049")
# https://github.com/OPM/opm-flowdiagnostics-applications
set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "1b521494ce9c09a1286dd0a81a1333caa123c680")
set(OPM_FLOWDIAGNOSTICS_APPLICATIONS_SHA "570601718e7197b751bc3cba60c1e5fb7d842842")
# https://github.com/OPM/opm-parser/blob/master/opm/parser/eclipse/Units/Units.hpp
# This file was moved from opm-core to opm-parser october 2016

View File

@ -33,6 +33,7 @@ list (APPEND TEST_SOURCE_FILES
)
list (APPEND EXAMPLE_SOURCE_FILES
examples/computeFlowStorageCurve.cpp
examples/computeLocalSolutions.cpp
examples/computeToFandTracers.cpp
examples/computeTracers.cpp

View File

@ -0,0 +1,79 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include "exampleSetup.hpp"
#include <opm/flowdiagnostics/DerivedQuantities.hpp>
#include <numeric>
// Syntax (typical):
// computeFlowStorageCurve case=<ecl_case_prefix> step=<report_number>
int main(int argc, char* argv[])
try {
example::Setup setup(argc, argv);
auto& fdTool = setup.toolbox;
// Solve for forward and reverse time of flight.
std::vector<Opm::FlowDiagnostics::CellSet> start;
auto fwd = fdTool.computeInjectionDiagnostics(start);
auto rev = fdTool.computeProductionDiagnostics(start);
// Give disconnected cells zero pore volume.
std::vector<int> nbcount(setup.graph.numCells(), 0);
for (int nb : setup.graph.neighbours()) {
if (nb >= 0) {
++nbcount[nb];
}
}
auto pv = setup.graph.poreVolume();
for (size_t i = 0; i < pv.size(); ++i) {
if (nbcount[i] == 0) {
pv[i] = 0.0;
}
}
// Cells that have more than 100 times the average pore volume are
// probably aquifers, we ignore them (again by setting pore volume
// to zero). If this is the correct thing to do or not could
// depend on what you want to use the diagnostic for.
const double average_pv = std::accumulate(pv.begin(), pv.end(), 0.0) / double(pv.size());
for (double& pvval : pv) {
if (pvval > 100.0 * average_pv) {
pvval = 0.0;
}
}
// Compute graph.
auto fphi = Opm::FlowDiagnostics::flowCapacityStorageCapacityCurve(fwd, rev, pv);
// Write it to standard out.
std::cout.precision(16);
const int sz = fphi.first.size();
for (int i = 0; i < sz; ++i) {
std::cout << fphi.first[i] << " " << fphi.second[i] << '\n';
}
}
catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << '\n';
}

View File

@ -42,9 +42,9 @@ try {
std::vector<int> completion_cells;
completion_cells.reserve(well.completions.size());
for (const auto& completion : well.completions) {
const int grid_index = completion.grid_index;
const auto& gridName = completion.gridName;
const auto& ijk = completion.ijk;
const int cell_index = setup.graph.activeCell(ijk, grid_index);
const int cell_index = setup.graph.activeCell(ijk, gridName);
if (cell_index >= 0) {
completion_cells.push_back(cell_index);
}

View File

@ -31,10 +31,10 @@
#include <opm/utility/ECLFluxCalc.hpp>
#include <opm/utility/ECLGraph.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/utility/ECLWellSolution.hpp>
#include <exception>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
@ -80,38 +80,66 @@ namespace example {
throw std::invalid_argument(os.str());
}
template <class FluxCalc>
inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G, const bool compute_fluxes)
extractFluxField(const Opm::ECLGraph& G,
FluxCalc&& getFlux)
{
using ConnVals = Opm::FlowDiagnostics::ConnectionValues;
const auto actPh = G.activePhases();
auto flux = ConnVals(ConnVals::NumConnections{G.numConnections()},
ConnVals::NumPhases{3});
ConnVals::NumPhases{actPh.size()});
auto phas = ConnVals::PhaseID{0};
Opm::ECLFluxCalc calc(G);
for (const auto& p : actPh) {
const auto pflux = getFlux(p);
const auto phases = { Opm::ECLGraph::PhaseIndex::Aqua ,
Opm::ECLGraph::PhaseIndex::Liquid ,
Opm::ECLGraph::PhaseIndex::Vapour };
for (const auto& p : phases)
{
const auto pflux = compute_fluxes ? calc.flux(p) : G.flux(p);
if (! pflux.empty()) {
assert (pflux.size() == flux.numConnections());
auto conn = ConnVals::ConnID{0};
for (const auto& v : pflux) {
flux(conn, phas) = v;
conn.id += 1;
}
}
phas.id += 1;
}
return flux;
}
inline Opm::FlowDiagnostics::ConnectionValues
extractFluxField(const Opm::ECLGraph& G,
const Opm::ECLRestartData& rstrt,
const bool compute_fluxes)
{
if (compute_fluxes) {
Opm::ECLFluxCalc calc(G);
auto getFlux = [&calc, &rstrt]
(const Opm::ECLGraph::PhaseIndex p)
{
return calc.flux(rstrt, p);
};
return extractFluxField(G, getFlux);
}
auto getFlux = [&G, &rstrt]
(const Opm::ECLGraph::PhaseIndex p)
{
return G.flux(rstrt, p);
};
return extractFluxField(G, getFlux);
}
template <class WellFluxes>
Opm::FlowDiagnostics::CellSetValues
extractWellFlows(const Opm::ECLGraph& G,
@ -120,9 +148,9 @@ namespace example {
Opm::FlowDiagnostics::CellSetValues inflow;
for (const auto& well : well_fluxes) {
for (const auto& completion : well.completions) {
const int grid_index = completion.grid_index;
const auto& gridName = completion.gridName;
const auto& ijk = completion.ijk;
const int cell_index = G.activeCell(ijk, grid_index);
const int cell_index = G.activeCell(ijk, gridName);
if (cell_index >= 0) {
// Since inflow is a std::map, if the key was not
// already present operator[] will insert a
@ -142,7 +170,7 @@ namespace example {
struct FilePaths
{
FilePaths(const Opm::parameter::ParameterGroup& param)
FilePaths(const Opm::ParameterGroup& param)
{
const string casename = param.getDefault<string>("case", "DEFAULT_CASE_NAME");
grid = param.has("grid") ? param.get<string>("grid")
@ -164,13 +192,13 @@ namespace example {
inline Opm::parameter::ParameterGroup
inline Opm::ParameterGroup
initParam(int argc, char** argv)
{
// Obtain parameters from command line (possibly specifying a parameter file).
const bool verify_commandline_syntax = true;
const bool parameter_output = false;
Opm::parameter::ParameterGroup param(argc, argv, verify_commandline_syntax, parameter_output);
Opm::ParameterGroup param(argc, argv, verify_commandline_syntax, parameter_output);
return param;
}
@ -180,10 +208,9 @@ namespace example {
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;
const auto I = Opm::ECLInitFileData(file_paths.init);
return Opm::ECLGraph::load(file_paths.grid, I);
}
@ -209,38 +236,47 @@ namespace example {
struct Setup
{
Setup(int argc, char** argv)
: param(initParam(argc, argv))
, file_paths(param)
, graph(initGraph(file_paths))
, well_fluxes()
, toolbox(initToolbox(graph))
: param (initParam(argc, argv))
, file_paths (param)
, rstrt (file_paths.restart)
, 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)) {
if (! this->selectReportStep(step)) {
std::ostringstream os;
os << "Report Step " << step
<< " is Not Available in Result Set '"
<< file_paths.grid.stem() << '\'';
<< " 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 {
if (! rstrt.selectReportStep(step)) {
return false;
}
{
auto wsol = Opm::ECLWellSolution{};
well_fluxes = wsol.solution(rstrt, graph.activeGrids());
}
Opm::parameter::ParameterGroup param;
toolbox.assignConnectionFlux(extractFluxField(graph, rstrt, compute_fluxes_));
toolbox.assignInflowFlux(extractWellFlows(graph, well_fluxes));
return true;
}
Opm::ParameterGroup param;
FilePaths file_paths;
Opm::ECLRestartData rstrt;
Opm::ECLGraph graph;
std::vector<Opm::ECLWellSolution::WellData> well_fluxes;
Opm::FlowDiagnostics::Toolbox toolbox;

View File

@ -25,20 +25,21 @@
#include <opm/core/utility/parameters/ParameterGroup.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <iostream>
// Syntax (typical):
// extractFromRestart unrst=<ecl_unrst file> step=<report_number> keyword=<keyword to dump> grid_id=<grid number>
int main(int argc, char* argv[]) {
try {
Opm::parameter::ParameterGroup param(argc, argv,
Opm::ParameterGroup param(argc, argv,
/*verify_commandline_syntax=*/ true,
/*parameter_output=*/ false);
const std::string unrst_file = param.get<std::string>("unrst");
const int report_step = param.getDefault("step", int(0));
const int grid_id = param.getDefault("grid_id", int(0));
const std::string grid_id = param.getDefault("grid_id", std::string(""));
const std::string keyword = param.get<std::string>("keyword");
Opm::ECLResultData restart_file(unrst_file);
Opm::ECLRestartData restart_file(unrst_file);
if (!restart_file.selectReportStep(report_step)) {
std::cerr << "Could not find report step " << report_step << "." << std::endl;

View File

@ -18,6 +18,8 @@
*/
#include <opm/utility/ECLFluxCalc.hpp>
#include <opm/utility/ECLResultData.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
namespace Opm
@ -34,11 +36,15 @@ namespace Opm
std::vector<double> ECLFluxCalc::flux(const PhaseIndex /* phase */) const
std::vector<double>
ECLFluxCalc::flux(const ECLRestartData& rstrt,
const PhaseIndex /* phase */) const
{
// Obtain dynamic data.
DynamicData dyn_data;
dyn_data.pressure = graph_.linearisedCellData("PRESSURE", &ECLUnits::UnitSystem::pressure);
dyn_data.pressure = graph_
.linearisedCellData(rstrt, "PRESSURE",
&ECLUnits::UnitSystem::pressure);
// Compute fluxes per connection.
const int num_conn = transmissibility_.size();

View File

@ -25,6 +25,7 @@
namespace Opm
{
class ECLRestartData;
/// Class for computing connection fluxes in the absence of flux output.
class ECLFluxCalc
@ -45,7 +46,9 @@ namespace Opm
/// \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;
std::vector<double>
flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const;
private:
struct DynamicData

View File

@ -1,6 +1,5 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
@ -39,6 +38,7 @@
#include <memory>
#include <sstream>
#include <stdexcept>
#include <unordered_map>
#include <utility>
#include <boost/filesystem.hpp>
@ -86,6 +86,17 @@ namespace {
const ecl_grid_type*
getGrid(const ecl_grid_type* G, const int gridID);
/// Retrieve grid name.
///
/// \param[in] ERT Grid representation.
///
/// \param[in] gridID Numeric index of requested grid. Zero for the
/// main grid or positive for one of the LGRs.
///
/// \return Name of grid \p G. Empty for the main grid.
std::string
getGridName(const ecl_grid_type* G, const int gridID);
/// Extract Cartesian dimensions of an ECL grid.
///
/// \param[in] G ERT grid instance corresponding to the model's main
@ -99,14 +110,20 @@ namespace {
/// Access unit conventions pertaining to single grid in result set.
///
/// \tparam ResultSet Type representing a result set. Must
/// implement methods \code haveKeywordData() \endcode and \code
/// keywordData<>() \endcode. Typically \code Opm::ECLRestartData
/// \endcode or \code Opm::ECLInitFileData \endcode.
///
/// \param[in] rset Result set.
///
/// \param[in] grid_ID Numerical ID of grid. Non-negative. Zero
/// for the main grid and positive for LGRs.
/// \param[in] gridID ID (name) of particular grid. Empty for the
/// main grid.
///
/// \return Unit system convention for \p grid_ID in result set.
auto getUnitSystem(const ::Opm::ECLResultData& rset,
const int grid_ID)
/// \return Unit system convention for \p gridID in result set.
template <class ResultSet>
auto getUnitSystem(const ResultSet& rset,
const std::string& gridID)
-> decltype(::Opm::ECLUnits::createUnitSystem(0));
/// Retrieve global pore-volume vector from INIT source.
@ -117,15 +134,15 @@ namespace {
///
/// \param[in] init ERT representation of INIT source.
///
/// \param[in] grid_ID Numerical ID of grid. Non-negative. Zero
/// for the main grid and positive in the case of an LGR.
/// \param[in] gridID ID (name) of grid. Empty for the main grid
/// and non-empty 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>
getPVolVector(const ecl_grid_type* G,
const ::Opm::ECLResultData& init,
const int grid_ID = 0);
const ::Opm::ECLInitFileData& init,
const std::string& gridID = "");
/// Extract non-neighbouring connections from ECLIPSE model
///
@ -138,8 +155,9 @@ namespace {
/// between main and local grids.
std::vector<ecl_nnc_type>
loadNNC(const ecl_grid_type* G,
const ::Opm::ECLResultData& init);
const ecl_file_type* init);
/// Cartesian connections in a model grid.
class CartesianGridData
{
public:
@ -154,7 +172,7 @@ namespace {
/// \param[in] gridID Numeric identifier of this grid. Zero for
/// main grid, positive for LGRs.
CartesianGridData(const ecl_grid_type* G,
const ::Opm::ECLResultData& init,
const ::Opm::ECLInitFileData& init,
const int gridID);
/// Retrieve non-negative numeric ID of grid instance.
@ -162,6 +180,8 @@ namespace {
/// \return Constructor's \c gridID parameter.
int gridID() const;
const std::string& gridName() const;
/// Retrieve number of active cells in graph.
std::size_t numCells() const;
@ -218,13 +238,14 @@ namespace {
///
/// \return Numerical values of result set vector, relative to
/// global cell numbering of this grid.
template <class ResultSet>
std::vector<double>
cellData(const ::Opm::ECLResultData& src,
cellData(const ResultSet& rset,
const std::string& vector) const;
template <typename T>
template <typename T, class ResultSet>
std::vector<T>
activeCellData(const ::Opm::ECLResultData& src,
activeCellData(const ResultSet& rset,
const std::string& vector) const;
/// Retrieve values of result set vector for all Cartesian
@ -238,7 +259,7 @@ namespace {
/// \return Numerical values of result set vector attributed to
/// all of the grid's Cartesian connections.
std::vector<double>
connectionData(const ::Opm::ECLResultData& src,
connectionData(const ::Opm::ECLRestartData& rstrt,
const std::string& vector,
const double unit) const;
@ -424,6 +445,9 @@ namespace {
/// than zero for LGRs.
const int gridID_;
/// Grid name. Mostly for querying properties in local grids.
const std::string gridName_;
/// Map results from active to global cells.
CartesianCells cells_;
@ -445,13 +469,18 @@ namespace {
/// Predicate for whether or not a particular result vector is
/// defined on the grid's cells.
///
/// \param[in] src Result set.
/// \tparam ResultSet Representation of an ECLIPSE result set.
/// Typically \code Opm::ECLInitFileData \endcode or \code
/// Opm::ECLRestartData \endcode.
///
/// \param[in] rset Result set.
///
/// \param[in] vector Name of result vector.
///
/// \return Whether or not \p vector is defined on model's
/// cells and part of the result set \p src.
bool haveCellData(const ::Opm::ECLResultData& src,
/// \return Whether or not \p vector is defined on model's cells
/// and part of the result set \p src.
template <class ResultSet>
bool haveCellData(const ResultSet& rset,
const std::string& keyword) const;
/// Predicate for whether or not a particular result vector is
@ -462,9 +491,9 @@ namespace {
/// \param[in] vector Prefix of result vector name.
///
/// \return Whether or not all vectors formed by \p vector plus
/// known directional suffixes are defined on model's cells and
/// part of the result set \p src.
bool haveConnData(const ::Opm::ECLResultData& src,
/// known directional suffixes are defined on model's cells
/// and part of the result set \p src.
bool haveConnData(const ::Opm::ECLRestartData& rstrt,
const std::string& keyword) const;
/// Append directional cell data to global collection of
@ -480,7 +509,7 @@ namespace {
/// input, collection of values corresponding to any previous
/// directions (preserved), and on output additionally contains
/// the data corresponding to the Cartesian direction \p d.
void connectionData(const ::Opm::ECLResultData& src,
void connectionData(const ::Opm::ECLRestartData& rstrt,
const CartesianCells::Direction d,
const std::string& vector,
const double unit,
@ -510,7 +539,7 @@ namespace {
///
/// \param[in] init Internalised
void deriveNeighbours(const std::vector<std::size_t>& gcells,
const ::Opm::ECLResultData& init,
const ::Opm::ECLInitFileData& init,
const CartesianCells::Direction d);
};
} // namespace ECL
@ -535,10 +564,20 @@ ECL::getGrid(const ecl_grid_type* G, const int gridID)
return ecl_grid_iget_lgr(G, gridID - 1);
}
std::string
ECL::getGridName(const ecl_grid_type* G, const int gridID)
{
if (gridID == ECL_GRID_MAINGRID_LGR_NR) {
return ""; // Empty for main grid.
}
return ecl_grid_get_name(G);
}
std::vector<double>
ECL::getPVolVector(const ecl_grid_type* G,
const ::Opm::ECLResultData& init,
const int gridID)
const ::Opm::ECLInitFileData& init,
const std::string& gridID)
{
auto make_szt = [](const int i)
{
@ -578,8 +617,8 @@ ECL::loadCase(const boost::filesystem::path& grid)
if (! G) {
std::ostringstream os;
os << "Failed to load ECL Grid from '"
<< grid.generic_string() << '\'';
os << "Failed to load ECL Grid from "
<< grid.generic_string();
throw std::invalid_argument(os.str());
}
@ -600,21 +639,40 @@ ECL::cartesianDimensions(const ecl_grid_type* G)
make_szt(ecl_grid_get_nz(G)) } };
}
auto ECL::getUnitSystem(const ::Opm::ECLResultData& rset,
const int grid_ID)
template <class ResultSet>
auto ECL::getUnitSystem(const ResultSet& rset,
const std::string& 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);
const auto ih = rset.template keywordData<int>(INTEHEAD_KW, grid_ID);
return ::Opm::ECLUnits::createUnitSystem(ih[INTEHEAD_UNIT_INDEX]);
const auto usys = ih[INTEHEAD_UNIT_INDEX];
const auto validUsys = (usys >= 1) && (usys <= 4);
if (! validUsys) {
if (! grid_ID.empty()) {
// Unity system not provided in local grid. Fall back to
// querying the main grid instead.
const auto mainGrid = std::string{ "" };
return getUnitSystem(rset, mainGrid);
}
throw std::out_of_range {
"No Valid Unit System Key in Result-Set"
};
}
return ::Opm::ECLUnits::createUnitSystem(usys);
}
std::vector<ecl_nnc_type>
ECL::loadNNC(const ecl_grid_type* G,
const ::Opm::ECLResultData& init)
const ecl_file_type* init)
{
auto make_szt = [](const int n)
{
@ -628,7 +686,7 @@ ECL::loadNNC(const ecl_grid_type* G,
if (numNNC > 0) {
nncData.resize(numNNC);
ecl_nnc_export(G, init.getRawFilePtr(), nncData.data());
ecl_nnc_export(G, init, nncData.data());
}
return nncData;
@ -891,10 +949,11 @@ CartesianCells::ind2sub(const std::size_t globalCell) const
ECL::CartesianGridData::
CartesianGridData(const ecl_grid_type* G,
const ::Opm::ECLResultData& init,
const ::Opm::ECLInitFileData& init,
const int gridID)
: gridID_(gridID)
, cells_ (G, ::ECL::getPVolVector(G, init, gridID_))
: gridID_ (gridID)
, gridName_(::ECL::getGridName(G, gridID))
, cells_ (G, ::ECL::getPVolVector(G, init, gridName_))
{
{
using VT = DirectionSuffix::value_type;
@ -923,6 +982,12 @@ int ECL::CartesianGridData::gridID() const
return this->gridID_;
}
const std::string&
ECL::CartesianGridData::gridName() const
{
return this->gridName_;
}
std::size_t
ECL::CartesianGridData::numCells() const
{
@ -973,47 +1038,50 @@ ECL::CartesianGridData::isSubdivided(const int cellID) const
return this->cells_.isSubdivided(cellID);
}
template <class ResultSet>
std::vector<double>
ECL::CartesianGridData::
cellData(const ::Opm::ECLResultData& src,
cellData(const ResultSet& rset,
const std::string& vector) const
{
if (! this->haveCellData(src, vector)) {
if (! this->haveCellData(rset, vector)) {
return {};
}
auto x = src.keywordData<double>(vector, this->gridID_);
const auto x =
rset.template keywordData<double>(vector, this->gridName());
return this->cells_.scatterToGlobal(x);
}
namespace { namespace ECL {
template <typename T>
template <typename T, class ResultSet>
std::vector<T>
CartesianGridData::activeCellData(const ::Opm::ECLResultData& src,
CartesianGridData::activeCellData(const ResultSet& rset,
const std::string& vector) const
{
if (! this->haveCellData(src, vector)) {
if (! this->haveCellData(rset, vector)) {
return {};
}
auto x = src.keywordData<T>(vector, this->gridID_);
auto x = rset.template keywordData<T>(vector, this->gridName());
return this->cells_.gatherToActive(std::move(x));
}
}} // namespace Anonymous::ECL
template <class ResultSet>
bool
ECL::CartesianGridData::
haveCellData(const ::Opm::ECLResultData& src,
haveCellData(const ResultSet& rset,
const std::string& vector) const
{
return src.haveKeywordData(vector, this->gridID_);
return rset.template haveKeywordData(vector, this->gridName());
}
bool
ECL::CartesianGridData::
haveConnData(const ::Opm::ECLResultData& src,
haveConnData(const ::Opm::ECLRestartData& rstrt,
const std::string& vector) const
{
auto have_data = true;
@ -1024,7 +1092,7 @@ haveConnData(const ::Opm::ECLResultData& src,
{
const auto vname = this->vectorName(vector, d);
have_data = this->haveCellData(src, vname);
have_data = this->haveCellData(rstrt, vname);
if (! have_data) { break; }
}
@ -1034,11 +1102,11 @@ haveConnData(const ::Opm::ECLResultData& src,
std::vector<double>
ECL::CartesianGridData::
connectionData(const ::Opm::ECLResultData& src,
connectionData(const ::Opm::ECLRestartData& rstrt,
const std::string& vector,
const double unit) const
{
if (! this->haveConnData(src, vector)) {
if (! this->haveConnData(rstrt, vector)) {
return {};
}
@ -1048,7 +1116,9 @@ connectionData(const ::Opm::ECLResultData& src,
CartesianCells::Direction::J ,
CartesianCells::Direction::K })
{
this->connectionData(src, d, this->vectorName(vector, d), unit, x);
const auto vname = this->vectorName(vector, d);
this->connectionData(rstrt, d, vname, unit, x);
}
return x;
@ -1056,13 +1126,13 @@ connectionData(const ::Opm::ECLResultData& src,
void
ECL::CartesianGridData::
connectionData(const ::Opm::ECLResultData& src,
connectionData(const ::Opm::ECLRestartData& rstrt,
const CartesianCells::Direction d,
const std::string& vector,
const double unit,
std::vector<double>& x) const
{
const auto v = this->cellData(src, vector);
const auto v = this->cellData(rstrt, vector);
const auto& cells = this->outCell_.find(d);
@ -1090,7 +1160,7 @@ vectorName(const std::string& vector,
void
ECL::CartesianGridData::
deriveNeighbours(const std::vector<std::size_t>& gcells,
const ::Opm::ECLResultData& init,
const ::Opm::ECLInitFileData& init,
const CartesianCells::Direction d)
{
auto tran = std::string{"TRAN"};
@ -1112,12 +1182,12 @@ deriveNeighbours(const std::vector<std::size_t>& gcells,
throw std::invalid_argument("Input direction must be (I,J,K)");
}
const auto& T = init.haveKeywordData(tran, this->gridID_)
const auto& T = init.haveKeywordData(tran, this->gridName())
? this->cellData(init, tran)
: std::vector<double>(this->cells_.numGlobalCells(), 1.0);
const auto trans_unit =
ECL::getUnitSystem(init, this->gridID_)->transmissibility();
ECL::getUnitSystem(init, this->gridName())->transmissibility();
auto SI_trans = [trans_unit](const double trans)
{
@ -1166,7 +1236,7 @@ public:
/// \param[in] grid Name or prefix of ECL grid (i.e., .GRID or
/// .EGRID) file.
///
/// \param[in] init Name of ECL INIT file corresponding to \p grid
/// \param[in] init ECL INIT result set corresponding to \p grid
/// input. Assumed to provide at least a complete set
/// of pore-volume values (i.e., for all global cells
/// defined in the \p grid).
@ -1174,13 +1244,8 @@ public:
/// If available in the INIT file, the constructor will
/// also leverage the transmissibility data when
/// constructing the active cell neighbourship table.
Impl(const Path& grid, const Path& init);
/// Assign source object for phase flux calculation.
///
/// \param[in] src Name of ECL restart file, possibly unified, from
/// which next set of phase fluxes should be retrieved.
void assignDataSource(const Path& src);
Impl(const boost::filesystem::path& grid,
const ECLInitFileData& init);
/// Retrieve number of grids.
///
@ -1200,7 +1265,7 @@ public:
/// ijk from specified grid. Negative one (-1) if (I,J,K) outside
/// valid range or if the specific cell identified by \p ijk and \p
/// gridID is not actually active.
int activeCell(const int gridID,
int activeCell(const std::string& gridID,
const std::array<int,3>& ijk) const;
/// Retrieve number of active cells in graph.
@ -1215,6 +1280,11 @@ public:
/// flux() may return non-zero values.
const std::vector<PhaseIndex>& activePhases() const;
/// Retrieve the simulation scenario's set of active grids.
///
/// Mostly for canonical lookup of result data in LGRs.
const std::vector<std::string>& activeGrids() const;
/// Retrieve neighbourship relations between active cells.
///
/// The \c i-th connection is between active cells \code
@ -1238,24 +1308,6 @@ public:
/// \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;
/// 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.
///
@ -1269,14 +1321,55 @@ public:
/// reported due to report frequencies or no flux values are output at
/// all).
std::vector<double>
flux(const PhaseIndex phase) const;
flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const;
template <typename T>
/// Retrieve result set vector from current view linearised on active
/// cells.
///
/// \tparam T Element type of result set vector.
///
/// \tparam ResultSet Implementation of an ECL Result Set. Typically
/// \code Opm::ECLRestartData \endcode or \code Opm::ECLInitFileData
/// \endcode.
///
/// \param[in] rset ECL Result set. Typically an instance of \code
/// Opm::ECLRestartData \endcode or \code Opm::ECLInitFileData
/// \endcode.
///
/// \param[in] vector Name of result set vector.
///
/// \return Result set vector linearised on active cells.
template <typename T, class ResultSet>
std::vector<T>
rawLinearisedCellData(const std::string& vector) const;
rawLinearisedCellData(const ResultSet& rset,
const std::string& vector) 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(rstrt, "PRESSURE", &ECLUnits::UnitSystem::pressure);
/// \endcode
///
/// \param[in] rstrt ECL Restart dataset. It is the responsibility of
/// the caller to ensure that the restart data is correctly
/// positioned on a particular report step.
///
/// \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,
linearisedCellData(const ECLRestartData& rstrt,
const std::string& vector,
UnitConvention unit) const;
private:
@ -1518,8 +1611,10 @@ private:
/// the simulation run.
std::vector<PhaseIndex> activePhases_;
/// Current result set.
std::unique_ptr<ECLResultData> src_;
/// Set of active grids in result set.
std::vector<std::string> activeGrids_;
std::unordered_map<std::string, int> gridID_;
/// Extract explicit non-neighbouring connections from ECL output.
///
@ -1528,15 +1623,13 @@ private:
/// \param[in] G ERT Grid representation.
///
/// \param[in] init ERT representation of INIT source.
///
/// \param[in] coll Backing data for neighbourship extraction.
void defineNNCs(const ecl_grid_type* G,
const ::Opm::ECLResultData& init);
const ECLInitFileData& init);
/// Extract scenario's set of active phases.
///
/// Writes to activePhases_.
void defineActivePhases(const ::Opm::ECLResultData& init);
void defineActivePhases(const ::Opm::ECLInitFileData& init);
/// Compute ECL vector basename for particular phase flux.
///
@ -1554,6 +1647,8 @@ private:
/// \tparam[in] GetFluxUnit Type of function object for computing the
/// grid-dependent flux unit.
///
/// \param[in] rstrt ECL Restart data result set.
///
/// \param[in] vector Result set vector prefix. Typically computed by
/// method flowVector().
///
@ -1572,7 +1667,8 @@ private:
/// contains those values that correspond to the non-neighbouring
/// connections (appended onto \p flux).
template <class GetFluxUnit>
void fluxNNC(const std::string& vector,
void fluxNNC(const ECLRestartData& rstrt,
const std::string& vector,
GetFluxUnit&& fluxUnit,
std::vector<double>& flux) const;
};
@ -1763,12 +1859,10 @@ isViable(const std::vector<ECL::CartesianGridData>& grids,
// ======================================================================
Opm::ECLGraph::Impl::Impl(const Path& grid, const Path& init)
Opm::ECLGraph::Impl::Impl(const boost::filesystem::path& grid,
const ECLInitFileData& init)
{
const auto G = ECL::loadCase(grid);
auto I = ::Opm::ECLResultData{ init };
I.selectGlobalView();
const auto numGrids = ECL::numGrids(G.get());
@ -1779,41 +1873,42 @@ Opm::ECLGraph::Impl::Impl(const Path& grid, const Path& init)
for (auto gridID = 0*numGrids; gridID < numGrids; ++gridID)
{
this->grid_.emplace_back(ECL::getGrid(G.get(), gridID),
I, gridID);
init, gridID);
this->activeOffset_.push_back(this->activeOffset_.back() +
this->grid_.back().numCells());
this->activeGrids_.push_back(this->grid_.back().gridName());
this->gridID_[this->activeGrids_.back()] = gridID;
}
this->defineNNCs(G.get(), I);
this->defineActivePhases(I);
}
void
Opm::ECLGraph::Impl::assignDataSource(const Path& src)
{
this->src_.reset(new ECLResultData(src));
this->defineNNCs(G.get(), init);
this->defineActivePhases(init);
}
int
Opm::ECLGraph::Impl::
numGrids() const
Opm::ECLGraph::Impl::numGrids() const
{
return grid_.size();
}
int
Opm::ECLGraph::Impl::
activeCell(const int gridID,
activeCell(const std::string& gridID,
const std::array<int,3>& ijk) const
{
const auto gIdx =
static_cast<decltype(this->grid_.size())>(gridID);
if (gIdx >= this->grid_.size()) {
const auto gID = this->gridID_.find(gridID);
if (gID == std::end(this->gridID_)) {
return -1;
}
const auto gIdx =
static_cast<decltype(this->grid_.size())>(gID->second);
assert ((gIdx <= this->grid_.size()) &&
"Logic Error in ECLGraph::Impl::Impl()");
const auto& grid = this->grid_[gIdx];
const auto active = grid.activeCell(ijk[0], ijk[1], ijk[2]);
@ -1851,6 +1946,12 @@ Opm::ECLGraph::Impl::activePhases() const
return this->activePhases_;
}
const std::vector<std::string>&
Opm::ECLGraph::Impl::activeGrids() const
{
return this->activeGrids_;
}
std::vector<int>
Opm::ECLGraph::Impl::neighbours() const
{
@ -1925,24 +2026,13 @@ Opm::ECLGraph::Impl::transmissibility() const
return trans;
}
const ::Opm::ECLResultData&
Opm::ECLGraph::Impl::rawResultData() const
{
return *this->src_;
}
bool Opm::ECLGraph::Impl::selectReportStep(const int rptstep) const
{
return this->src_->selectReportStep(rptstep);
}
std::vector<double>
Opm::ECLGraph::Impl::
flux(const PhaseIndex phase) const
Opm::ECLGraph::Impl::flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const
{
auto fluxUnit = [this](const int gridID)
auto fluxUnit = [&rstrt](const std::string& gridID)
{
return ::ECL::getUnitSystem(*this->src_, gridID)->reservoirRate();
return ::ECL::getUnitSystem(rstrt, gridID)->reservoirRate();
};
const auto vector = this->flowVector(phase);
@ -1956,7 +2046,7 @@ flux(const PhaseIndex phase) const
for (const auto& G : this->grid_) {
const auto& q =
G.connectionData(*this->src_, vector, fluxUnit(G.gridID()));
G.connectionData(rstrt, vector, fluxUnit(G.gridName()));
if (q.empty()) {
// Flux vector invalid unless all grids provide this result
@ -1971,7 +2061,7 @@ flux(const PhaseIndex phase) const
// Model includes non-neighbouring connections such as faults and/or
// local grid refinement. Extract pertinent flux values for these
// connections.
this->fluxNNC(vector, std::move(fluxUnit), v);
this->fluxNNC(rstrt, vector, std::move(fluxUnit), v);
}
if (v.size() < totconn) {
@ -1985,14 +2075,15 @@ flux(const PhaseIndex phase) const
namespace Opm {
template <typename T>
template <typename T, class ResultSet>
std::vector<T>
ECLGraph::Impl::rawLinearisedCellData(const std::string& vector) const
ECLGraph::Impl::rawLinearisedCellData(const ResultSet& rset,
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);
const auto xi = G.activeCellData<T>(rset, vector);
x.insert(x.end(), std::begin(xi), std::end(xi));
}
@ -2006,19 +2097,20 @@ namespace Opm {
} // namespace Opm
std::vector<double>
Opm::ECLGraph::Impl::linearisedCellData(const std::string& vector,
Opm::ECLGraph::Impl::linearisedCellData(const ECLRestartData& rstrt,
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);
const auto xi = G.activeCellData<double>(rstrt, vector);
if (xi.empty()) { continue; }
// Note: Compensate for incrementing Grid ID above.
const auto usys =
ECL::getUnitSystem(*this->src_, G.gridID());
ECL::getUnitSystem(rstrt, G.gridName());
// Note: 'usys' (generally, std::unique_ptr<>) does not support
// regular PMF syntax (i.e., operator->*()).
@ -2041,22 +2133,25 @@ Opm::ECLGraph::Impl::linearisedCellData(const std::string& vector,
void
Opm::ECLGraph::Impl::defineNNCs(const ecl_grid_type* G,
const ::Opm::ECLResultData& init)
const ECLInitFileData& init)
{
// Assume all transmissibilites in the result set follow the same unit
// conventions.
const auto trans_unit =
ECL::getUnitSystem(init, 0)->transmissibility();
const auto gridID = std::string{ "" }; // Empty in main grid.
for (const auto& nnc : ECL::loadNNC(G, init)) {
const auto trans_unit =
ECL::getUnitSystem(init, gridID)->transmissibility();
for (const auto& nnc : ECL::loadNNC(G, init.getRawFilePtr())) {
this->nnc_.add(this->grid_, this->activeOffset_, trans_unit, nnc);
}
}
template <class GetFluxUnit>
void
Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
Opm::ECLGraph::Impl::fluxNNC(const ECLRestartData& rstrt,
const std::string& vector,
GetFluxUnit&& fluxUnit,
std::vector<double>& flux) const
{
@ -2068,28 +2163,31 @@ Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
const auto fluxID = rel.makeKeyword(vector);
for (const auto& G : this->grid_) {
const auto gridID = G.gridID();
const auto& gridName = G.gridName();
const auto& iset =
rel.indexSet().getGridCollection(gridID);
rel.indexSet().getGridCollection(G.gridID());
if (iset.empty()) {
// No NNCs for this category in this grid. Skip.
if (iset.empty() ||
! rstrt.haveKeywordData(fluxID, gridName))
{
// No NNCs for this category in this grid or corresponding
// flux vector does not exist. Skip.
continue;
}
// Note: Method name is confusing, but does actually do what we
// want here.
const auto q = G.cellData(*this->src_, fluxID);
const auto q = rstrt.keywordData<double>(fluxID, gridName);
if (q.empty()) {
// No flux data for this category in this grid. Skip.
// Empty flux data for this category in this grid. Not
// really supposed to happen if the above check fires, but
// skip this (category,gridID) pair nonetheless.
continue;
}
const auto flux_unit = fluxUnit(gridID);
const auto flux_unit = fluxUnit(gridName);
// Data fully available for (category,gridID). Assign
// Data fully available for (category,gridName). Assign
// approriate subset of NNC flux vector.
for (const auto& ix : iset) {
assert (ix.neighIdx < v.size());
@ -2118,10 +2216,12 @@ Opm::ECLGraph::Impl::fluxNNC(const std::string& vector,
void
Opm::ECLGraph::Impl::
defineActivePhases(const ::Opm::ECLResultData& init)
defineActivePhases(const ::Opm::ECLInitFileData& init)
{
const auto gridID = std::string{ "" }; // Empty in main grid.
const auto ih =
init.keywordData<int>(INTEHEAD_KW, ECL_GRID_MAINGRID_LGR_NR);
init.keywordData<int>(INTEHEAD_KW, gridID);
const auto phaseMask =
static_cast<unsigned int>(ih[INTEHEAD_PHASE_INDEX]);
@ -2143,8 +2243,7 @@ defineActivePhases(const ::Opm::ECLResultData& init)
}
std::string
Opm::ECLGraph::Impl::
flowVector(const PhaseIndex phase) const
Opm::ECLGraph::Impl::flowVector(const PhaseIndex phase) const
{
const auto vector = std::string("FLR"); // Flow-rate, reservoir
@ -2193,40 +2292,32 @@ Opm::ECLGraph::operator=(ECLGraph&& rhs)
}
Opm::ECLGraph
Opm::ECLGraph::load(const Path& grid, const Path& init)
Opm::ECLGraph::load(const boost::filesystem::path& grid,
const ECLInitFileData& init)
{
auto pImpl = ImplPtr{new Impl(grid, init)};
return { std::move(pImpl) };
}
int
Opm::ECLGraph::numGrids() const
int Opm::ECLGraph::numGrids() const
{
return this->pImpl_->numGrids();
}
int
Opm::ECLGraph::activeCell(const std::array<int,3>& ijk,
const int gridID) const
const std::string& gridID) const
{
return this->pImpl_->activeCell(gridID, ijk);
}
void
Opm::ECLGraph::assignFluxDataSource(const Path& src)
{
this->pImpl_->assignDataSource(src);
}
std::size_t
Opm::ECLGraph::numCells() const
std::size_t Opm::ECLGraph::numCells() const
{
return this->pImpl_->numCells();
}
std::size_t
Opm::ECLGraph::numConnections() const
std::size_t Opm::ECLGraph::numConnections() const
{
return this->pImpl_->numConnections();
}
@ -2237,6 +2328,12 @@ Opm::ECLGraph::activePhases() const
return this->pImpl_->activePhases();
}
const std::vector<std::string>&
Opm::ECLGraph::activeGrids() const
{
return this->pImpl_->activeGrids();
}
std::vector<int> Opm::ECLGraph::neighbours() const
{
return this->pImpl_->neighbours();
@ -2252,45 +2349,47 @@ std::vector<double> Opm::ECLGraph::transmissibility() const
return this->pImpl_->transmissibility();
}
bool Opm::ECLGraph::selectReportStep(const int rptstep) const
{
return this->pImpl_->selectReportStep(rptstep);
}
const Opm::ECLResultData&
Opm::ECLGraph::rawResultData() const
{
return this->pImpl_->rawResultData();
}
std::vector<double>
Opm::ECLGraph::
flux(const PhaseIndex phase) const
Opm::ECLGraph::flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const
{
return this->pImpl_->flux(phase);
return this->pImpl_->flux(rstrt, phase);
}
namespace Opm {
template <typename T>
template <typename T, class ResultSet>
std::vector<T>
ECLGraph::rawLinearisedCellData(const std::string& vector) const
ECLGraph::rawLinearisedCellData(const ResultSet& rset,
const std::string& vector) const
{
return this->pImpl_->rawLinearisedCellData<T>(vector);
return this->pImpl_->rawLinearisedCellData<T>(rset, vector);
}
// Explicit instantiations for the element types we care about.
// Explicit instantiations of method rawLinearisedCellData() for the
// element and result set types we care about.
template std::vector<int>
ECLGraph::rawLinearisedCellData<int>(const std::string& vector) const;
ECLGraph::rawLinearisedCellData<int>(const ECLInitFileData& rset,
const std::string& vector) const;
template std::vector<int>
ECLGraph::rawLinearisedCellData<int>(const ECLRestartData& rset,
const std::string& vector) const;
template std::vector<double>
ECLGraph::rawLinearisedCellData<double>(const std::string& vector) const;
ECLGraph::rawLinearisedCellData<double>(const ECLInitFileData& rset,
const std::string& vector) const;
template std::vector<double>
ECLGraph::rawLinearisedCellData<double>(const ECLRestartData& rset,
const std::string& vector) const;
} // namespace Opm
std::vector<double>
Opm::ECLGraph::linearisedCellData(const std::string& vector,
Opm::ECLGraph::linearisedCellData(const ECLRestartData& rstrt,
const std::string& vector,
UnitConvention unit) const
{
return this->pImpl_->linearisedCellData(vector, unit);
return this->pImpl_->linearisedCellData(rstrt, vector, unit);
}

View File

@ -1,6 +1,5 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
@ -27,6 +26,7 @@
#include <array>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
@ -45,8 +45,6 @@ namespace Opm {
class ECLGraph
{
public:
using Path = boost::filesystem::path;
/// Enum for indicating requested phase from the flux() method.
enum class PhaseIndex { Aqua = 0, Liquid = 1, Vapour = 2 };
@ -93,15 +91,10 @@ namespace Opm {
/// \return Fully formed ECLIPSE connection graph with property
/// associations.
static ECLGraph
load(const Path& grid, const Path& init);
load(const boost::filesystem::path& gridFile,
const ECLInitFileData& init);
/// Assign source object for phase flux calculation.
///
/// \param[in] src Name of ECL restart file, possibly unified, from
/// which next set of phase fluxes should be retrieved.
void assignFluxDataSource(const Path& src);
/// Retrieve number of grids.
/// Retrieve number of grids in model.
///
/// \return The number of LGR grids plus one (the main grid).
int numGrids() const;
@ -120,7 +113,7 @@ namespace Opm {
/// outside valid range or if the specific cell identified by \p
/// ijk and \p gridID is not actually active.
int activeCell(const std::array<int,3>& ijk,
const int gridID = 0) const;
const std::string& gridID = 0) const;
/// Retrieve number of active cells in graph.
std::size_t numCells() const;
@ -134,6 +127,11 @@ namespace Opm {
/// which flux() will return non-zero values if data available.
const std::vector<PhaseIndex>& activePhases() const;
/// Retrieve the simulation scenario's set of active grids.
///
/// Mostly for canonical lookup of result data in LGRs.
const std::vector<std::string>& activeGrids() const;
/// Retrieve neighbourship relations between active cells.
///
/// The \c i-th connection is between active cells \code
@ -157,24 +155,6 @@ namespace Opm {
/// \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
/// assignFluxDataSource().
bool selectReportStep(const int rptstep) const;
/// 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.
///
@ -186,7 +166,8 @@ namespace Opm {
/// output to the restart file). Numerical values in SI units
/// (rm^3/s).
std::vector<double>
flux(const PhaseIndex phase) const;
flux(const ECLRestartData& rstrt,
const PhaseIndex phase) const;
/// Retrieve result set vector from current view (e.g., particular
/// report step) linearised on active cells.
@ -196,9 +177,10 @@ namespace Opm {
/// \param[in] vector Name of result set vector.
///
/// \return Result set vector linearised on active cells.
template <typename T>
template <typename T, class ResultSet>
std::vector<T>
rawLinearisedCellData(const std::string& vector) const;
rawLinearisedCellData(const ResultSet& rset,
const std::string& vector) const;
/// Convenience type alias for \c UnitSystem PMFs (pointer to member
/// function).
@ -211,9 +193,13 @@ namespace Opm {
/// Typical call:
/// \code
/// const auto press =
/// lCD("PRESSURE", &ECLUnits::UnitSystem::pressure);
/// lCD(rstrt, "PRESSURE", &ECLUnits::UnitSystem::pressure);
/// \endcode
///
/// \param[in] rstrt ECL Restart dataset. It is the responsibility
/// of the caller to ensure that the restart data is correctly
/// positioned on a particular report step.
///
/// \param[in] vector Name of result set vector.
///
/// \param[in] unit Call-back hook in \c UnitSystem implementation
@ -223,7 +209,8 @@ namespace Opm {
/// \return Result set vector linearised on active cells, converted
/// to strict SI unit conventions.
std::vector<double>
linearisedCellData(const std::string& vector,
linearisedCellData(const ECLRestartData& rstrt,
const std::string& vector,
UnitConvention unit) const;
private:

View File

@ -1,6 +1,5 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).
@ -18,8 +17,8 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ECLRESTARTDATA_HEADER_INCLUDED
#define OPM_ECLRESTARTDATA_HEADER_INCLUDED
#ifndef OPM_ECLRESULTDATA_HEADER_INCLUDED
#define OPM_ECLRESULTDATA_HEADER_INCLUDED
#include <memory>
#include <string>
@ -34,14 +33,17 @@
/// Forward-declaration of ERT's representation of an ECLIPSE result file.
///
/// This is a hole in the insulation between the interface and the
/// underlying implementation of class ECLResultData.
/// underlying implementation of class ECLInitFileData and furthermore
/// enables constructing wrapper objects from separately parsed result sets.
extern "C" {
typedef struct ecl_file_struct ecl_file_type;
} // extern "C"
namespace Opm {
/// Representation of an ECLIPSE result-set.
class ECLGraph;
/// Representation of an ECLIPSE Restart result-set.
///
/// This class is aware of the internal structure of ECLIPSE restart
/// files and may restrict its operation to a single report step. The
@ -51,30 +53,37 @@ namespace Opm {
///
/// Note: The client must select a view of the result-set before
/// accessing any vectors within the set.
class ECLResultData
class ECLRestartData
{
public:
using Path = boost::filesystem::path;
/// Default constructor disabled.
ECLResultData() = delete;
ECLRestartData() = delete;
/// Constructor.
///
/// \param[in] casePrefix Name or prefix of ECL result data.
explicit ECLResultData(const Path& casePrefix);
/// Owning semantics.
///
/// \param[in] rstrt Name or prefix of ECL result data.
explicit ECLRestartData(boost::filesystem::path rstrt);
/// Constructor
///
/// Shared ownership of result set.
///
/// \param[in] rstrt ECL restart result set.
explicit ECLRestartData(std::shared_ptr<ecl_file_type> rstrt);
/// Copy constructor.
///
/// \param[in] rhs Object from which to construct new instance.
ECLResultData(const ECLResultData& rhs);
ECLRestartData(const ECLRestartData& rhs);
/// Move constructor.
///
/// \param[in,out] rhs Object from which to construct new instance.
/// Its internal implementation will be subsumed into the new
/// object.
ECLResultData(ECLResultData&& rhs);
ECLRestartData(ECLRestartData&& rhs);
/// Assignment operator.
///
@ -82,7 +91,7 @@ namespace Opm {
/// instance.
///
/// \return \code *this \endcode.
ECLResultData& operator=(const ECLResultData& rhs);
ECLRestartData& operator=(const ECLRestartData& rhs);
/// Move assignment operator.
///
@ -91,27 +100,10 @@ namespace Opm {
/// instance.
///
/// \return \code *this \endcode.
ECLResultData& operator=(ECLResultData&& rhs);
ECLRestartData& operator=(ECLRestartData&& rhs);
/// Destructor.
~ECLResultData();
/// Access the underlying ERT representation of the result-set.
///
/// This is essentially a hole in the interface that is intended to
/// support a few very specialised uses. Handle with care.
///
/// \return Handle to underlying ERT representation of result-set.
const ecl_file_type* getRawFilePtr() const;
/// Reset the object's internal view of the result-set to encompass
/// the entirety of the underlying file's result vectors.
///
/// This is mostly useful in the case of accessing static result
/// sets such as INIT files.
///
/// \return Whether or not generating the global view succeeded.
bool selectGlobalView();
~ECLRestartData();
/// Select a result-set view that corresponds to a single report
/// step.
@ -123,7 +115,7 @@ namespace Opm {
/// \return Whether or not selecting the report step succeeded. The
/// typical failure case is the report step not being available
/// in the result-set.
bool selectReportStep(const int step);
bool selectReportStep(const int step) const;
/// Query current result-set view for availability of particular
/// named result vector in particular enumerated grid.
@ -132,12 +124,12 @@ namespace Opm {
/// availability.
///
/// \param[in] gridID Identity of specific grid for which to query
/// data availability.
/// data availability. Empty for the main grid.
///
/// \return Whether or not keyword data for the named result vector
/// is available in the specific grid.
bool haveKeywordData(const std::string& vector,
const int gridID) const;
const std::string& gridID = "") const;
/// Retrieve current result-set view's data values for particular
/// named result vector in particular enumerated grid.
@ -159,13 +151,13 @@ namespace Opm {
/// keyword data.
///
/// \param[in] gridID Identity of specific grid for which to
/// retrieve keyword data.
/// retrieve keyword data. Empty for the main grid.
///
/// \return Keyword data values. Empty if type conversion fails.
template <typename T>
std::vector<T>
keywordData(const std::string& vector,
const int gridID) const;
const std::string& gridID = "") const;
private:
class Impl;
@ -173,6 +165,119 @@ namespace Opm {
std::unique_ptr<Impl> pImpl_;
};
/// Representation of an ECLIPSE Initialization result-set.
///
/// This class is aware of the internal structure of ECLIPSE INIT files
/// and queries only those objects that pertain to a single grid.
class ECLInitFileData
{
public:
ECLInitFileData() = delete;
/// Constructor.
///
/// Construct from filename. Owning semantics.
///
/// \param[in] casePrefix Name or prefix of ECL result data.
explicit ECLInitFileData(boost::filesystem::path initFile);
/// Constructor.
///
/// Construct from dataset already input through other means.
///
/// Non-owning/shared ownership semantics.
explicit ECLInitFileData(std::shared_ptr<ecl_file_type> initFile);
/// Copy constructor.
///
/// \param[in] rhs Object from which to construct new instance.
ECLInitFileData(const ECLInitFileData& rhs);
/// Move constructor.
///
/// \param[in,out] rhs Object from which to construct new instance.
/// Its internal implementation will be subsumed into the new
/// object.
ECLInitFileData(ECLInitFileData&& rhs);
/// Assignment operator.
///
/// \param[in] rhs Object from which to assign new values to current
/// instance.
///
/// \return \code *this \endcode.
ECLInitFileData& operator=(const ECLInitFileData& rhs);
/// Move assignment operator.
///
/// \param[in,out] Object from which to assign new instance values.
/// Its internal implementation will be subsumed into this
/// instance.
///
/// \return \code *this \endcode.
ECLInitFileData& operator=(ECLInitFileData&& rhs);
/// Destructor.
~ECLInitFileData();
/// Query current result-set view for availability of particular
/// named result vector in particular enumerated grid.
///
/// \param[in] vector Named result vector for which to query data
/// availability.
///
/// \param[in] gridID Identity of specific grid for which to query
/// data availability. Empty for the main grid.
///
/// \return Whether or not keyword data for the named result vector
/// is available in the specific grid.
bool haveKeywordData(const std::string& vector,
const std::string& gridID = "") const;
/// Retrieve current result-set view's data values for particular
/// named result vector in particular enumerated grid.
///
/// Will fail (throw an exception of type std::invalid_argument)
/// unless the requested keyword data is available in the specific
/// grid in the current active view.
///
/// \tparam T Element type of return value. The underlying keyword
/// data will be converted to this type if needed and possible.
/// Note that some type combinations do not make sense. It is,
/// for instance, not possible to retrieve keyword values of an
/// underlying arithmetic type in the form of a \code
/// std::vector<std::string> \endcode. Similarly, we cannot
/// access underlying character data through elements of an
/// arithmetic type (e.g., \code std::vector<double> \endcode.)
///
/// \param[in] vector Named result vector for which to retrieve
/// keyword data.
///
/// \param[in] gridID Identity of specific grid for which to
/// retrieve keyword data. Empty for the main grid.
///
/// \return Keyword data values. Empty if type conversion fails.
template <typename T>
std::vector<T>
keywordData(const std::string& vector,
const std::string& gridID = "") const;
// Grant class ECLGraph privileged access to getRawFilePtr().
friend class ECLGraph;
private:
class Impl;
std::unique_ptr<Impl> pImpl_;
/// Access the underlying ERT representation of the result-set.
///
/// This is essentially a hole in the interface that is intended to
/// support a few very specialised uses. Handle with care.
///
/// \return Handle to underlying ERT representation of result-set.
const ecl_file_type* getRawFilePtr() const;
};
} // namespace Opm
#endif // OPM_ECLRESTARTDATA_HEADER_INCLUDED
#endif // OPM_ECLRESULTDATA_HEADER_INCLUDED

View File

@ -1,5 +1,4 @@
/*
Copyright 2017 SINTEF ICT, Applied Mathematics.
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).

View File

@ -1,5 +1,4 @@
/*
Copyright 2017 SINTEF ICT, Applied Mathematics.
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media Project (OPM).

View File

@ -1,6 +1,6 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
@ -146,13 +146,16 @@ namespace Opm
std::vector<ECLWellSolution::WellData>
ECLWellSolution::solution(const ECLResultData& restart,
const int num_grids) const
ECLWellSolution::solution(const ECLRestartData& restart,
const std::vector<std::string>& grids) const
{
// Note: this function expects to be called with the correct restart
// block--e.g., a report step--selected in the caller.
// Read well data for global grid.
std::vector<WellData> all_wd{};
for (int grid_index = 0; grid_index < num_grids; ++grid_index) {
std::vector<WellData> wd = readWellData(restart, grid_index);
for (const auto& gridName : grids) {
std::vector<WellData> wd = readWellData(restart, gridName);
// Append to set of all well data.
all_wd.insert(all_wd.end(), wd.begin(), wd.end());
}
@ -163,24 +166,34 @@ namespace Opm
std::vector<ECLWellSolution::WellData>
ECLWellSolution::readWellData(const ECLResultData& restart, const int grid_index) const
ECLWellSolution::readWellData(const ECLRestartData& restart,
const std::string& gridName) const
{
// Note: this function is expected to be called in a context
// where the correct restart block using the ert block mechanisms.
// Check if result set provides complete set of well solution data.
if (! (restart.haveKeywordData(ZWEL_KW, gridName) &&
restart.haveKeywordData(IWEL_KW, gridName) &&
restart.haveKeywordData("XWEL" , gridName) &&
restart.haveKeywordData(ICON_KW, gridName) &&
restart.haveKeywordData("XCON" , gridName)))
{
// Not all requisite keywords present in this grid. Can't
// create a well solution.
return {};
}
// Read header, return if trivial.
INTEHEAD ih(restart.keywordData<int>(INTEHEAD_KW, grid_index));
INTEHEAD ih(restart.keywordData<int>(INTEHEAD_KW, gridName));
if (ih.nwell == 0) {
return {};
}
const double qr_unit = resRateUnit(ih.unit);
// Read necessary keywords.
auto zwel = restart.keywordData<std::string>(ZWEL_KW, grid_index);
auto iwel = restart.keywordData<int> (IWEL_KW, grid_index);
auto xwel = restart.keywordData<double> ("XWEL" , grid_index);
auto icon = restart.keywordData<int> (ICON_KW, grid_index);
auto xcon = restart.keywordData<double> ("XCON" , grid_index);
// Load well topology and flow rates.
auto zwel = restart.keywordData<std::string>(ZWEL_KW, gridName);
auto iwel = restart.keywordData<int> (IWEL_KW, gridName);
auto xwel = restart.keywordData<double> ("XWEL" , gridName);
auto icon = restart.keywordData<int> (ICON_KW, gridName);
auto xcon = restart.keywordData<double> ("XCON" , gridName);
// Create well data.
std::vector<WellData> wd_vec;
@ -204,7 +217,7 @@ namespace Opm
const int xcon_offset = (well*ih.ncwma + comp_index) * ih.nxcon;
WellData::Completion completion;
// Note: subtracting 1 from indices (Fortran -> C convention).
completion.grid_index = grid_index;
completion.gridName = gridName;
completion.ijk = { icon[icon_offset + ICON_I_INDEX] - 1,
icon[icon_offset + ICON_J_INDEX] - 1,
icon[icon_offset + ICON_K_INDEX] - 1 };
@ -212,7 +225,7 @@ namespace Opm
completion.reservoir_inflow_rate = -unit::convert::from(xcon[xcon_offset + XCON_QR_INDEX], qr_unit);
if (disallow_crossflow_) {
// Add completion only if not cross-flowing (injecting producer or producing injector).
if (completion.reservoir_inflow_rate < 0.0 == is_producer) {
if ((completion.reservoir_inflow_rate < 0.0) == is_producer) {
wd.completions.push_back(completion);
}
} else {

View File

@ -1,6 +1,6 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
@ -29,7 +29,7 @@
namespace Opm
{
class ECLResultData;
class ECLRestartData;
class ECLWellSolution
{
@ -47,7 +47,7 @@ namespace Opm
bool is_injector_well;
struct Completion
{
int grid_index; // 0 for main grid, otherwise LGR grid.
std::string gridName; // empty for main grid, otherwise LGR grid.
std::array<int, 3> ijk; // Cartesian location in grid.
double reservoir_inflow_rate; // Total fluid rate in SI (m^3/s).
};
@ -58,8 +58,8 @@ namespace Opm
///
/// Will throw if required data is not available for the
/// requested step.
std::vector<WellData> solution(const ECLResultData& restart,
const int num_grids) const;
std::vector<WellData> solution(const ECLRestartData& restart,
const std::vector<std::string>& grids) const;
private:
// Data members.
@ -67,8 +67,8 @@ namespace Opm
bool disallow_crossflow_;
// Methods.
std::vector<WellData> readWellData(const ECLResultData& restart,
const int grid_index) const;
std::vector<WellData> readWellData(const ECLRestartData& restart,
const std::string& gridName) const;
};

View File

@ -250,7 +250,7 @@ namespace {
}
ErrorTolerance
testTolerances(const ::Opm::parameter::ParameterGroup& param)
testTolerances(const ::Opm::ParameterGroup& param)
{
const auto atol = param.getDefault("atol", 1.0e-8);
const auto rtol = param.getDefault("rtol", 5.0e-12);
@ -277,7 +277,7 @@ namespace {
}
ReferenceToF
loadReference(const ::Opm::parameter::ParameterGroup& param,
loadReference(const ::Opm::ParameterGroup& param,
const int step,
const int nDigits)
{

View File

@ -377,7 +377,7 @@ namespace {
}
ErrorTolerance
testTolerances(const ::Opm::parameter::ParameterGroup& param)
testTolerances(const ::Opm::ParameterGroup& param)
{
const auto atol = param.getDefault("atol", 1.0e-8);
const auto rtol = param.getDefault("rtol", 5.0e-12);
@ -386,7 +386,7 @@ namespace {
}
std::vector<std::string>
testQuantities(const ::Opm::parameter::ParameterGroup& param)
testQuantities(const ::Opm::ParameterGroup& param)
{
return StringUtils::VectorValue::
get<std::string>(param.get<std::string>("quant"));
@ -411,7 +411,7 @@ namespace {
}
ReferenceSolution
loadReference(const ::Opm::parameter::ParameterGroup& param,
loadReference(const ::Opm::ParameterGroup& param,
const std::string& quant,
const int step,
const int nDigits)
@ -493,7 +493,8 @@ namespace {
std::array<AggregateErrors, 2>
sampleDifferences(const ::Opm::ECLGraph& graph,
const ::Opm::parameter::ParameterGroup& param,
const ::Opm::ECLRestartData& rstrt,
const ::Opm::ParameterGroup& param,
const std::string& quant,
const std::vector<int>& steps)
{
@ -510,7 +511,7 @@ namespace {
auto E = std::array<AggregateErrors, 2>{};
for (const auto& step : steps) {
if (! graph.selectReportStep(step)) {
if (! rstrt.selectReportStep(step)) {
continue;
}
@ -518,7 +519,7 @@ namespace {
{
const auto raw = Calculated {
graph.rawLinearisedCellData<double>(ECLquant)
graph.rawLinearisedCellData<double>(rstrt, ECLquant)
};
computeErrors(Reference{ ref.raw }, raw, E[0]);
@ -526,7 +527,7 @@ namespace {
{
const auto SI = Calculated {
graph.linearisedCellData(ECLquant, unit)
graph.linearisedCellData(rstrt, ECLquant, unit)
};
computeErrors(Reference{ ref.SI }, SI, E[1]);
@ -561,12 +562,14 @@ try {
const auto pth = example::FilePaths(prm);
const auto tol = testTolerances(prm);
const auto rstrt = ::Opm::ECLRestartData(pth.restart);
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 E =
sampleDifferences(graph, rstrt, prm, quant, steps);
const auto ok =
everythingFine(E[0], tol) && everythingFine(E[1], tol);

View File

@ -152,7 +152,7 @@ namespace {
}
ErrorTolerance
testTolerances(const ::Opm::parameter::ParameterGroup& param)
testTolerances(const ::Opm::ParameterGroup& param)
{
const auto atol = param.getDefault("atol", 1.0e-8);
const auto rtol = param.getDefault("rtol", 5.0e-12);
@ -161,7 +161,7 @@ namespace {
}
std::vector<double>
loadReference(const ::Opm::parameter::ParameterGroup& param)
loadReference(const ::Opm::ParameterGroup& param)
{
namespace fs = boost::filesystem;
@ -185,7 +185,7 @@ namespace {
};
}
bool transfieldAcceptable(const ::Opm::parameter::ParameterGroup& param,
bool transfieldAcceptable(const ::Opm::ParameterGroup& param,
const std::vector<double>& trans)
{
const auto Tref = loadReference(param);

View File

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2015, 2016, 2017 SINTEF ICT, Applied Mathematics.
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
@ -70,9 +71,26 @@ namespace FlowDiagnostics
const Toolbox::Reverse& producer_solution,
const std::vector<double>& pv)
{
const auto& ftof = injector_solution.fd.timeOfFlight();
const auto& rtof = producer_solution.fd.timeOfFlight();
if (pv.size() != ftof.size() || pv.size() != rtof.size()) {
return flowCapacityStorageCapacityCurve(injector_solution.fd.timeOfFlight(),
producer_solution.fd.timeOfFlight(),
pv);
}
/// The F-Phi curve.
///
/// The F-Phi curve is an analogue to the fractional flow
/// curve in a 1D displacement. It can be used to compute
/// other interesting diagnostic quantities such as the Lorenz
/// coefficient. For a technical description see Shavali et
/// al. (SPE 146446), Shook and Mitchell (SPE 124625).
Graph flowCapacityStorageCapacityCurve(const std::vector<double>& injector_tof,
const std::vector<double>& producer_tof,
const std::vector<double>& pv)
{
if (pv.size() != injector_tof.size() || pv.size() != producer_tof.size()) {
throw std::runtime_error("flowCapacityStorageCapacityCurve(): "
"Input solutions must have same size.");
}
@ -82,12 +100,12 @@ namespace FlowDiagnostics
typedef std::pair<double, double> D2;
std::vector<D2> time_and_pv(n);
for (int ii = 0; ii < n; ++ii) {
time_and_pv[ii].first = ftof[ii] + rtof[ii]; // Total travel time.
time_and_pv[ii].first = injector_tof[ii] + producer_tof[ii]; // Total travel time.
time_and_pv[ii].second = pv[ii];
}
std::sort(time_and_pv.begin(), time_and_pv.end());
auto Phi = cumulativeNormalized(time_and_pv, [](const D2& i) { return i.first; });
auto Phi = cumulativeNormalized(time_and_pv, [](const D2& i) { return i.second; });
auto F = cumulativeNormalized(time_and_pv, [](const D2& i) { return i.second / i.first; });
return Graph{std::move(Phi), std::move(F)};

View File

@ -50,6 +50,14 @@ namespace FlowDiagnostics
const Toolbox::Reverse& producer_solution,
const std::vector<double>& pore_volume);
/// This overload gets the injector and producer time-of-flight
/// directly instead of extracting it from the solution
/// objects. It otherwise behaves identically to the other
/// overload.
Graph flowCapacityStorageCapacityCurve(const std::vector<double>& injector_tof,
const std::vector<double>& producer_tof,
const std::vector<double>& pore_volume);
/// The Lorenz coefficient from the F-Phi curve.
///
/// The Lorenz coefficient is a measure of heterogeneity. It

View File

@ -1,5 +1,6 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
This file is part of the Open Porous Media project (OPM).

View File

@ -1,6 +1,6 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
Copyright 2016, 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).

View File

@ -1,5 +1,6 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
This file is part of the Open Porous Media project (OPM).

View File

@ -247,6 +247,8 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
BOOST_CHECK_THROW(flowCapacityStorageCapacityCurve(fwd, rev, {}), std::runtime_error);
const auto fcapscap = flowCapacityStorageCapacityCurve(fwd, rev, pv);
check_is_close(fcapscap, expectedFPhi);
const auto fcapscap2 = flowCapacityStorageCapacityCurve(fwd.fd.timeOfFlight(), rev.fd.timeOfFlight(), pv);
check_is_close(fcapscap2, expectedFPhi);
BOOST_TEST_MESSAGE("==== Lorenz coefficient");
const double expectedLorenz = 0.0;
@ -296,4 +298,28 @@ BOOST_AUTO_TEST_CASE (OneDimCase)
}
BOOST_AUTO_TEST_CASE (GeneralCase)
{
BOOST_TEST_MESSAGE("==== F-Phi graph");
std::vector<double> pv { 1.0, 2.0, 1.0 };
std::vector<double> ftof { 0.0, 2.0, 1.0 };
std::vector<double> rtof { 1.0, 2.0, 0.0 };
const Graph expectedFPhi{
{ 0.0, 0.25, 0.5, 1.0 },
{ 0.0, 0.4, 0.8, 1.0 }
};
const auto fcapscap = flowCapacityStorageCapacityCurve(ftof, rtof, pv);
check_is_close(fcapscap, expectedFPhi);
BOOST_TEST_MESSAGE("==== Lorenz coefficient");
const double expectedLorenz = 0.3;
BOOST_CHECK_CLOSE(lorenzCoefficient(fcapscap), expectedLorenz, 1e-10);
}
BOOST_AUTO_TEST_SUITE_END()