/*
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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 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(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
struct StringTo;
template <>
struct StringTo
{
static int value(const std::string& s);
};
template <>
struct StringTo
{
static double value(const std::string& s);
};
template <>
struct StringTo
{
static std::string value(const std::string& s);
};
int StringTo::value(const std::string& s)
{
return std::stoi(s);
}
double StringTo::value(const std::string& s)
{
return std::stod(s);
}
std::string StringTo::value(const std::string& s)
{
return trim(s);
}
namespace VectorValue {
template
std::vector get(const std::string& s, std::true_type)
{
return split(s);
}
template
std::vector get(const std::string& s, std::false_type)
{
const auto tokens = split(s);
auto ret = std::vector{};
ret.reserve(tokens.size());
for (const auto& token : tokens) {
ret.push_back(StringTo::value(token));
}
return ret;
}
template
std::vector get(const std::string& s)
{
return get(s, typename std::is_same::type());
}
} // namespace VectorValue
} // namespace StringUtils
namespace {
struct PoreVolume
{
std::vector data;
};
class VectorDifference
{
public:
using Vector = std::vector;
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 VectorRatio
{
public:
using size_type = typename std::decay<
decltype(std::declval()[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 absolute;
std::vector relative;
};
struct ReferenceToF
{
std::vector forward;
std::vector reverse;
};
template
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
double pointMetric(const FieldVariable& x)
{
static_assert(std::is_same::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
availableReportSteps(const example::FilePaths& paths)
{
using FilePtr = ::ERT::
ert_unique_ptr;
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(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(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& 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(m))) + 1;
}
ReferenceToF
loadReference(const ::Opm::parameter::ParameterGroup& param,
const int step,
const int nDigits)
{
namespace fs = boost::filesystem;
using VRef = std::reference_wrapper>;
auto fname = fs::path(param.get("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{{ std::ref(tof.forward) ,
std::ref(tof.reverse) }};
{
auto i = static_cast(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& 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::type;
using Vector2 = std::decay::type;
using Ratio = VectorRatio;
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
sampleDifferences(example::Setup&& setup,
const std::vector& steps)
{
const auto start =
std::vector{};
const auto nDigits = numDigits(steps);
const auto pv = PoreVolume{ setup.graph.poreVolume() };
auto E = std::array{};
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& 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;
}