diff --git a/include/evalhyd/evald.hpp b/include/evalhyd/evald.hpp index e347f2596b7234bbc271d4d80a13274cb2a78ccb..5e629998c12de5bfbe6bf6490214cfb6c770d446 100644 --- a/include/evalhyd/evald.hpp +++ b/include/evalhyd/evald.hpp @@ -70,7 +70,7 @@ namespace evalhyd // check that the metrics to be computed are valid utils::check_metrics( metrics, - {"NSE"} + {"RMSE", "NSE", "KGE", "KGEPRIME"} ); // instantiate determinist evaluator @@ -81,7 +81,12 @@ namespace evalhyd std::unordered_map<std::string, std::vector<std::string>> dep; // register potentially recurring computation elt across metrics - // TODO + elt["RMSE"] = {"quad_err"}; + elt["NSE"] = {"mean_obs", "quad_obs", "quad_err"}; + elt["KGE"] = {"mean_obs", "mean_prd", "quad_obs", "quad_prd", + "r_pearson", "bias"}; + elt["KGEPRIME"] = {"mean_obs", "mean_prd", "quad_obs", "quad_prd", + "r_pearson", "bias"}; // register nested metrics (i.e. metric dependent on another metric) // TODO @@ -95,7 +100,20 @@ namespace evalhyd // pre-compute required elt for ( const auto& element : req_elt ) { - // TODO + if ( element == "mean_obs" ) + evaluator.calc_mean_obs(); + else if ( element == "mean_prd" ) + evaluator.calc_mean_prd(); + else if ( element == "quad_err" ) + evaluator.calc_quad_err(); + else if ( element == "quad_obs" ) + evaluator.calc_quad_obs(); + else if ( element == "quad_prd" ) + evaluator.calc_quad_prd(); + else if ( element == "r_pearson" ) + evaluator.calc_r_pearson(); + else if ( element == "bias" ) + evaluator.calc_bias(); } // pre-compute required dep @@ -109,13 +127,34 @@ namespace evalhyd for ( const auto& metric : metrics ) { - if ( metric == "NSE" ) + if ( metric == "RMSE" ) + { + if (std::find(req_dep.begin(), req_dep.end(), metric) + == req_dep.end()) + evaluator.calc_RMSE(); + r.emplace_back(evaluator.RMSE); + } + else if ( metric == "NSE" ) { if (std::find(req_dep.begin(), req_dep.end(), metric) == req_dep.end()) evaluator.calc_NSE(); r.emplace_back(evaluator.NSE); } + else if ( metric == "KGE" ) + { + if (std::find(req_dep.begin(), req_dep.end(), metric) + == req_dep.end()) + evaluator.calc_KGE(); + r.emplace_back(evaluator.KGE); + } + else if ( metric == "KGEPRIME" ) + { + if (std::find(req_dep.begin(), req_dep.end(), metric) + == req_dep.end()) + evaluator.calc_KGEPRIME(); + r.emplace_back(evaluator.KGEPRIME); + } } return r; diff --git a/src/determinist/evaluator.hpp b/src/determinist/evaluator.hpp index 800c128472ef5b6a513b000a357f1b24ea50035b..7af6357fe2fb5b8cbce45b50ddda933151270cc1 100644 --- a/src/determinist/evaluator.hpp +++ b/src/determinist/evaluator.hpp @@ -16,7 +16,13 @@ namespace evalhyd const A& q_obs; const A& q_prd; // members for computational elements - // TODO + A mean_obs; + A mean_prd; + A quad_err; + A quad_obs; + A quad_prd; + A r_pearson; + A bias; public: // constructor method @@ -35,13 +41,86 @@ namespace evalhyd } }; // members for evaluation metrics + A RMSE; A NSE; + A KGE; + A KGEPRIME; // methods to compute elements - // TODO + void calc_mean_obs(); + void calc_mean_prd(); + void calc_quad_err(); + void calc_quad_obs(); + void calc_quad_prd(); + void calc_r_pearson(); + void calc_bias(); // methods to compute metrics + void calc_RMSE(); void calc_NSE(); + void calc_KGE(); + void calc_KGEPRIME(); }; + template <class A> + void Evaluator<A>::calc_mean_obs() + { + mean_obs = xt::mean(q_obs, -1, xt::keep_dims); + } + + template <class A> + void Evaluator<A>::calc_mean_prd() + { + mean_prd = xt::mean(q_prd, -1, xt::keep_dims); + } + + template <class A> + void Evaluator<A>::calc_quad_err() + { + quad_err = xt::square(q_obs - q_prd); + } + + template <class A> + void Evaluator<A>::calc_quad_obs() + { + quad_obs = xt::square(q_obs - mean_obs); + } + + template <class A> + void Evaluator<A>::calc_quad_prd() + { + quad_prd = xt::square(q_prd - mean_prd); + } + + template <class A> + void Evaluator<A>::calc_r_pearson() + { + // calculate error in timing and dynamics $r_{pearson}$ + // (Pearson's correlation coefficient) + auto r_num = xt::sum( + (q_prd - mean_prd) * (q_obs - mean_obs), -1, xt::keep_dims + ); + auto r_den = xt::sqrt( + xt::sum(quad_prd, -1, xt::keep_dims) + * xt::sum(quad_obs, -1, xt::keep_dims) + ); + + r_pearson = r_num / r_den; + } + + template <class A> + void Evaluator<A>::calc_bias() + { + // calculate $bias$ + bias = xt::sum(q_prd, -1, xt::keep_dims) + / xt::sum(q_obs, -1, xt::keep_dims); + } + + template <class A> + void Evaluator<A>::calc_RMSE() + { + // compute RMSE + RMSE = xt::sqrt(xt::mean(quad_err, -1, xt::keep_dims)); + } + // Compute the Nash-Sutcliffe Efficiency (NSE). // // If multi-dimensional arrays are provided, the arrays of predictions @@ -55,22 +134,51 @@ namespace evalhyd // \require q_prd: // Array of streamflow predictions. // shape: (..., time) - // \assign nse + // \assign nse: // Array of computed Nash-Sutcliffe efficiencies. - // shape: (...) + // shape: (..., 1) template <class A> void Evaluator<A>::calc_NSE() { - // compute average observed flow - A q_avg = xt::mean(q_obs, -1, xt::keep_dims); - // compute squared errors operands - A f_num = xt::sum(xt::square(q_obs - q_prd), -1, xt::keep_dims); - A f_den = xt::sum(xt::square(q_obs - q_avg), -1, xt::keep_dims); + A f_num = xt::sum(quad_err, -1, xt::keep_dims); + A f_den = xt::sum(quad_obs, -1, xt::keep_dims); - // return computed NSE + // compute NSE NSE = 1 - (f_num / f_den); } + + template <class A> + void Evaluator<A>::calc_KGE() + { + // calculate error in spread of flow $alpha$ + auto alpha = + xt::stddev(q_prd, {q_prd.dimension() - 1}, xt::keep_dims) + / xt::stddev(q_obs, {q_obs.dimension() - 1}, xt::keep_dims); + + // compute KGE + KGE = 1 - xt::sqrt( + xt::square(r_pearson - 1) + + xt::square(alpha - 1) + + xt::square(bias - 1) + ); + } + + template <class A> + void Evaluator<A>::calc_KGEPRIME() + { + // calculate error in spread of flow $alpha$ + auto gamma = + (xt::stddev(q_prd, {q_prd.dimension() - 1}, xt::keep_dims) / mean_prd) + / (xt::stddev(q_obs, {q_obs.dimension() - 1}, xt::keep_dims) / mean_obs); + + // compute KGE + KGEPRIME = 1 - xt::sqrt( + xt::square(r_pearson - 1) + + xt::square(gamma - 1) + + xt::square(bias - 1) + ); + } } } diff --git a/tests/test_determinist.cpp b/tests/test_determinist.cpp index 1ad4ff60a8b8b8ebdf65b695bdaa9842c18ab764..a0078c59a616a1d5472d82fa1ee3b8ccbbeccbb7 100644 --- a/tests/test_determinist.cpp +++ b/tests/test_determinist.cpp @@ -8,7 +8,7 @@ #include "evalhyd/evald.hpp" -TEST(DeterministTests, TestNSE) { +TEST(DeterministTests, TestNSE1D2D) { // read in data std::ifstream ifs; ifs.open("./data/q_obs.csv"); @@ -37,10 +37,69 @@ TEST(DeterministTests, TestNSE) { ); // check results (both with 2D and 1D tensors) - xt::xtensor<double, 1> nse_2d = - {0.71891219, 0.7190249, 0.71835777, 0.71810361, 0.71776748}; - EXPECT_TRUE(xt::allclose(metrics_2d[0], nse_2d)); + xt::xtensor<double, 2> nse = + {{0.71891219}, + {0.7190249}, + {0.71835777}, + {0.71810361}, + {0.71776748}}; + EXPECT_TRUE(xt::allclose(metrics_2d[0], nse)); xt::xtensor<double, 1> nse_1d = {0.71891219}; - EXPECT_TRUE(xt::allclose(metrics_1d[0], nse_1d)); + EXPECT_TRUE(xt::allclose(metrics_1d[0], nse[0])); +} + +TEST(DeterministTests, TestMetrics) { + // read in data + std::ifstream ifs; + ifs.open("./data/q_obs.csv"); + xt::xtensor<double, 2> observed =xt::load_csv<int>(ifs); + ifs.close(); + + ifs.open("./data/q_prd.csv"); + xt::xtensor<double, 2> predicted_2d = xt::view( + xt::load_csv<double>(ifs), xt::range(0, 5), xt::all() + ); + ifs.close(); + + xt::xtensor<double, 1> predicted_1d = xt::row(predicted_2d, 0); + + // compute scores (both with 2D and 1D tensors) + std::vector<xt::xarray<double>> metrics = + evalhyd::evald<xt::xtensor<double, 2>>( + observed, predicted_2d, {"RMSE", "NSE", "KGE", "KGEPRIME"} + ); + + // check results on all metrics + xt::xtensor<double, 2> rmse = + {{777.034272}, + {776.878479}, + {777.800217}, + {778.151082}, + {778.61487}}; + EXPECT_TRUE(xt::allclose(metrics[0], rmse)); + + xt::xtensor<double, 2> nse = + {{0.718912}, + {0.719025}, + {0.718358}, + {0.718104}, + {0.717767}}; + EXPECT_TRUE(xt::allclose(metrics[1], nse)); + + xt::xtensor<double, 2> kge = + {{0.748088}, + {0.746106}, + {0.744111}, + {0.743011}, + {0.741768}}; + EXPECT_TRUE(xt::allclose(metrics[2], kge)); + + xt::xtensor<double, 2> kgeprime = + {{0.813141}, + {0.812775}, + {0.812032}, + {0.811787}, + {0.811387}}; + EXPECT_TRUE(xt::allclose(metrics[3], kgeprime)); }