diff --git a/include/evalhyd/detail/utils.hpp b/include/evalhyd/detail/utils.hpp index 64934cab69a06e81fe19bbb923578244f2d06b51..a5bb9b3fe910951d93778c9577973ba9147fe059 100644 --- a/include/evalhyd/detail/utils.hpp +++ b/include/evalhyd/detail/utils.hpp @@ -52,6 +52,13 @@ namespace evalhyd "number of samples missing for bootstrap" ); } + auto n_samples = bootstrap.find("n_samples")->second; + if (n_samples < 1) + { + throw std::runtime_error( + "number of samples must be greater than zero" + ); + } // check len_sample if (bootstrap.find("len_sample") == bootstrap.end()) { @@ -59,6 +66,13 @@ namespace evalhyd "length of sample missing for bootstrap" ); } + auto len_sample = bootstrap.find("len_sample")->second; + if (len_sample < 1) + { + throw std::runtime_error( + "length of sample must be greater than zero" + ); + } // check summary if (bootstrap.find("summary") == bootstrap.end()) { @@ -66,9 +80,9 @@ namespace evalhyd "summary missing for bootstrap" ); } - auto s = bootstrap.find("summary")->second; + auto summary = bootstrap.find("summary")->second; // TODO: change upper bound when mean+stddev and quantiles implemented - if ((s < 0) || (s > 0)) + if ((summary < 0) || (summary > 0)) { throw std::runtime_error( "invalid value for bootstrap summary" diff --git a/include/evalhyd/evald.hpp b/include/evalhyd/evald.hpp index 8b0041b6a9667f8d89532dc1ff4eb38d3006ae8a..88e0868ad057731c770ffe530a799082974ab658 100644 --- a/include/evalhyd/evald.hpp +++ b/include/evalhyd/evald.hpp @@ -4,6 +4,7 @@ #include <unordered_map> #include <vector> +#include <xtl/xoptional.hpp> #include <xtensor/xexpression.hpp> #include <xtensor/xtensor.hpp> #include <xtensor/xarray.hpp> @@ -58,18 +59,17 @@ namespace evalhyd /// /// exponent: ``double``, optional /// The value of the exponent n to use when the *transform* is the - /// power function. If not provided (or set to default value 1), - /// the streamflow observations and predictions remain untransformed. + /// power function. If not provided, the streamflow observations + /// and predictions remain untransformed. /// /// epsilon: ``double``, optional /// The value of the small constant ε to add to both the streamflow /// observations and predictions prior to the calculation of the /// *metrics* when the *transform* is the reciprocal function, the /// natural logarithm, or the power function with a negative exponent - /// (since none are defined for 0). If not provided (or set to default - /// value -9), one hundredth of the mean of the streamflow - /// observations is used as value for epsilon, as recommended by - /// `Pushpalatha et al. (2012) + /// (since none are defined for 0). If not provided, one hundredth of + /// the mean of the streamflow observations is used as value for + /// epsilon, as recommended by `Pushpalatha et al. (2012) /// <https://doi.org/10.1016/j.jhydrol.2011.11.055>`_. /// /// t_msk: ``XB2``, optional @@ -159,13 +159,16 @@ namespace evalhyd const xt::xexpression<XD2>& q_obs, const xt::xexpression<XD2>& q_prd, const std::vector<std::string>& metrics, - const std::string& transform = "none", - double exponent = 1, - double epsilon = -9, + xtl::xoptional<const std::string, bool> transform = + xtl::missing<const std::string>(), + xtl::xoptional<double, bool> exponent = + xtl::missing<double>(), + xtl::xoptional<double, bool> epsilon = + xtl::missing<double>(), const xt::xexpression<XB2>& t_msk = XB2({}), const xt::xtensor<std::array<char, 32>, 1>& m_cdt = {}, - const std::unordered_map<std::string, int>& bootstrap = - {{"n_samples", -9}, {"len_sample", -9}, {"summary", 0}}, + xtl::xoptional<const std::unordered_map<std::string, int>> bootstrap = + xtl::missing<const std::unordered_map<std::string, int>>(), const std::vector<std::string>& dts = {} ) { @@ -196,7 +199,10 @@ namespace evalhyd ); // check that optional parameters are valid - utils::check_bootstrap(bootstrap); + if (bootstrap.has_value()) + { + utils::check_bootstrap(bootstrap.value()); + } // check that data dimensions are compatible // > time @@ -277,68 +283,90 @@ namespace evalhyd // apply streamflow transformation if requested auto q_transform = [&](const XD2& q) { - if ( transform == "none" || (transform == "pow" && exponent == 1)) - { - return q; - } - else if ( transform == "sqrt" ) - { - return XD2(xt::sqrt(q)); - } - else if ( transform == "inv" ) + if (transform.has_value()) { - if ( epsilon == -9 ) + if ( transform.value() == "sqrt" ) { - // determine an epsilon value to avoid zero divide - epsilon = xt::mean(q_obs_)() * 0.01; + return XD2(xt::sqrt(q)); } - - return XD2(1. / (q + epsilon)); - } - else if ( transform == "log" ) - { - if ( epsilon == -9 ) + else if ( transform.value() == "inv" ) { - // determine an epsilon value to avoid log zero - epsilon = xt::mean(q_obs_)() * 0.01; - } + if ( !epsilon.has_value() ) + { + // determine an epsilon value to avoid zero divide + epsilon = xt::mean(q_obs_)() * 0.01; + } - return XD2(xt::log(q + epsilon)); - } - else if ( transform == "pow" ) - { - if ( exponent < 0 ) + return XD2(1. / (q + epsilon.value())); + } + else if ( transform.value() == "log" ) { - if ( epsilon == -9 ) + if ( !epsilon.has_value() ) { - // determine an epsilon value to avoid zero divide + // determine an epsilon value to avoid log zero epsilon = xt::mean(q_obs_)() * 0.01; } - return XD2(xt::pow(q + epsilon, exponent)); + return XD2(xt::log(q + epsilon.value())); + } + else if ( transform.value() == "pow" ) + { + if ( exponent.has_value() ) + { + if ( exponent.value() == 1) + { + return q; + } + else if ( exponent.value() < 0 ) + { + if ( !epsilon.has_value() ) + { + // determine an epsilon value to avoid zero divide + epsilon = xt::mean(q_obs_)() * 0.01; + } + + return XD2(xt::pow(q + epsilon.value(), + exponent.value())); + } + else + { + return XD2(xt::pow(q, exponent.value())); + } + } + else + { + throw std::runtime_error( + "missing exponent for power transformation" + ); + } } else { - return XD2(xt::pow(q, exponent)); + throw std::runtime_error( + "invalid streamflow transformation: " + + transform.value() + ); } } else { - throw std::runtime_error( - "invalid streamflow transformation: " + transform - ); + return q; } }; - const XD2 obs = q_transform(q_obs_); - const XD2 prd = q_transform(q_prd_); + const XD2& obs = q_transform(q_obs_); + const XD2& prd = q_transform(q_prd_); // generate bootstrap experiment if requested std::vector<xt::xkeep_slice<int>> exp; - auto n_samples = bootstrap.find("n_samples")->second; - auto len_sample = bootstrap.find("len_sample")->second; - if ((n_samples != -9) && (len_sample != -9)) + int summary; + + if (bootstrap.has_value()) { + auto n_samples = bootstrap.value().find("n_samples")->second; + auto len_sample = bootstrap.value().find("len_sample")->second; + summary = bootstrap.value().find("summary")->second; + if (dts.empty()) { throw std::runtime_error( @@ -354,6 +382,7 @@ namespace evalhyd { // if no bootstrap requested, generate one sample // containing all the time indices once + summary = 0; xt::xtensor<int, 1> all = xt::arange(n_tim); exp.push_back(xt::keep(all)); } @@ -364,8 +393,6 @@ namespace evalhyd // retrieve or compute requested metrics std::vector<xt::xarray<double>> r; - auto summary = bootstrap.find("summary")->second; - for ( const auto& metric : metrics ) { if ( metric == "RMSE" ) diff --git a/include/evalhyd/evalp.hpp b/include/evalhyd/evalp.hpp index a425a6692e23cfc9f1790511a7d51cd9a2730aee..02505cb8f2bb2d72dc112dc8c94e2961b3700b1a 100644 --- a/include/evalhyd/evalp.hpp +++ b/include/evalhyd/evalp.hpp @@ -4,6 +4,7 @@ #include <unordered_map> #include <vector> +#include <xtl/xoptional.hpp> #include <xtensor/xexpression.hpp> #include <xtensor/xtensor.hpp> #include <xtensor/xarray.hpp> @@ -144,8 +145,8 @@ namespace evalhyd const xt::xexpression<XD2>& q_thr = XD2({}), const xt::xexpression<XB4>& t_msk = XB4({}), const xt::xtensor<std::array<char, 32>, 2>& m_cdt = {}, - const std::unordered_map<std::string, int>& bootstrap = - {{"n_samples", -9}, {"len_sample", -9}, {"summary", 0}}, + xtl::xoptional<const std::unordered_map<std::string, int>> bootstrap = + xtl::missing<const std::unordered_map<std::string, int>>(), const std::vector<std::string>& dts = {} ) { @@ -184,7 +185,10 @@ namespace evalhyd // check that optional parameters are given as arguments utils::evalp::check_optionals(metrics, q_thr_); - utils::check_bootstrap(bootstrap); + if (bootstrap.has_value()) + { + utils::check_bootstrap(bootstrap.value()); + } // check that data dimensions are compatible // > time @@ -266,8 +270,8 @@ namespace evalhyd std::size_t n_thr = q_thr_.shape(1); std::size_t n_msk = t_msk_.size() > 0 ? t_msk_.shape(2) : (m_cdt.size() > 0 ? m_cdt.shape(1) : 1); - std::size_t n_exp = bootstrap.find("n_samples")->second == -9 ? 1: - bootstrap.find("n_samples")->second; + std::size_t n_exp = !bootstrap.has_value() ? 1: + bootstrap.value().find("n_samples")->second; // register metrics number of dimensions std::unordered_map<std::string, std::vector<std::size_t>> dim; @@ -308,10 +312,14 @@ namespace evalhyd // generate bootstrap experiment if requested std::vector<xt::xkeep_slice<int>> b_exp; - auto n_samples = bootstrap.find("n_samples")->second; - auto len_sample = bootstrap.find("len_sample")->second; - if ((n_samples != -9) && (len_sample != -9)) + int summary; + + if (bootstrap.has_value()) { + auto n_samples = bootstrap.value().find("n_samples")->second; + auto len_sample = bootstrap.value().find("len_sample")->second; + summary = bootstrap.value().find("summary")->second; + if (dts.empty()) { throw std::runtime_error( @@ -325,6 +333,7 @@ namespace evalhyd { // if no bootstrap requested, generate one sample // containing all the time indices once + summary = 0; xt::xtensor<int, 1> all = xt::arange(n_tim); b_exp.push_back(xt::keep(all)); } @@ -336,8 +345,6 @@ namespace evalhyd r.emplace_back(xt::zeros<double>(dim[metric])); } - auto summary = bootstrap.find("summary")->second; - // compute variables one site at a time to minimise memory imprint for (std::size_t s = 0; s < n_sit; s++) {