/* 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; }