-
Thibault Hallouin authored
An earlier implementation of the masking conditions assumed that the conditions on streamflow would only be on the observations, but this is not always the case. For example, reliability scores cannot be done on the observed streamflow and need to be performed on the predicted streamflow. So this is now possible as the condition syntax is changed and now *q_obs*/*q_prd_median*/*q_prd_mean* in place of *q*.
f31664dd
#include <fstream>
#include <vector>
#include <array>
#include <gtest/gtest.h>
#include <xtensor/xtensor.hpp>
#include <xtensor/xview.hpp>
#include <xtensor/xmanipulation.hpp>
#include <xtensor/xcsv.hpp>
#include "evalhyd/evald.hpp"
using namespace xt::placeholders; // required for `_` to work
TEST(DeterministTests, TestMetrics2D)
{
// 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 = xt::view(
xt::load_csv<double>(ifs), xt::range(0, 5), xt::all()
);
ifs.close();
// compute scores (with 2D tensors)
std::vector<xt::xarray<double>> metrics =
evalhyd::evald<2>(
observed, predicted, {"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));
}
TEST(DeterministTests, TestMetrics1D)
{
// read in data
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
std::ifstream ifs;
ifs.open("./data/q_obs.csv");
xt::xtensor<double, 1> observed = xt::squeeze(xt::load_csv<int>(ifs));
ifs.close();
ifs.open("./data/q_prd.csv");
xt::xtensor<double, 1> predicted = xt::row(
xt::view(xt::load_csv<double>(ifs), xt::range(0, 5), xt::all()), 0
);
ifs.close();
// compute scores (with 1D tensors)
std::vector<xt::xarray<double>> metrics =
evalhyd::evald<1>(
observed, predicted, {"RMSE", "NSE", "KGE", "KGEPRIME"}
);
// check results on all metrics
xt::xtensor<double, 1> rmse = {777.034272};
EXPECT_TRUE(xt::allclose(metrics[0], rmse));
xt::xtensor<double, 1> nse = {0.718912};
EXPECT_TRUE(xt::allclose(metrics[1], nse));
xt::xtensor<double, 1> kge = {0.748088};
EXPECT_TRUE(xt::allclose(metrics[2], kge));
xt::xtensor<double, 1> kgeprime = {0.813141};
EXPECT_TRUE(xt::allclose(metrics[3], kgeprime));
}
TEST(DeterministTests, TestTransform)
{
// 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 = xt::view(
xt::load_csv<double>(ifs), xt::range(0, 5), xt::all()
);
ifs.close();
// compute and check results on square-rooted streamflow series
std::vector<xt::xarray<double>> metrics =
evalhyd::evald<2>(observed, predicted, {"NSE"}, "sqrt");
xt::xtensor<double, 2> nse_sqrt =
{{0.882817},
{0.883023},
{0.883019},
{0.883029},
{0.882972}};
EXPECT_TRUE(xt::allclose(metrics[0], nse_sqrt));
// compute and check results on inverted streamflow series
metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "inv");
xt::xtensor<double, 2> nse_inv =
{{0.737323},
{0.737404},
{0.737429},
{0.737546},
{0.737595}};
EXPECT_TRUE(xt::allclose(metrics[0], nse_inv));
// compute and check results on square-rooted streamflow series
metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "log");
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
xt::xtensor<double, 2> nse_log =
{{0.893344},
{0.893523},
{0.893585},
{0.893758},
{0.893793}};
EXPECT_TRUE(xt::allclose(metrics[0], nse_log));
// compute and check results on power-transformed streamflow series
metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "pow", 0.2);
xt::xtensor<double, 2> nse_pow =
{{0.899207},
{0.899395},
{0.899451},
{0.899578},
{0.899588}};
EXPECT_TRUE(xt::allclose(metrics[0], nse_pow));
}
TEST(DeterministTests, TestMasks)
{
// 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 = xt::load_csv<double>(ifs);
ifs.close();
// generate temporal subset by dropping 20 first time steps
xt::xtensor<double, 2> masks =
xt::ones<bool>({std::size_t {1}, std::size_t {observed.size()}});
xt::view(masks, 0, xt::range(0, 20)) = 0;
// compute scores using masks to subset whole record
std::vector<std::string> metrics =
{"RMSE", "NSE", "KGE", "KGEPRIME"};
std::vector<xt::xarray<double>> metrics_masked =
evalhyd::evald<2>(observed, predicted, metrics, "none", 1, -9, masks);
// compute scores on pre-computed subset of whole record
xt::xtensor<double, 2> obs = xt::view(observed, xt::all(), xt::range(20, _));
xt::xtensor<double, 2> prd = xt::view(predicted, xt::all(), xt::range(20, _));
std::vector<xt::xarray<double>> metrics_subset =
evalhyd::evald<2>(obs, prd, metrics);
// check results are identical
for (int m = 0; m < metrics.size(); m++)
{
EXPECT_TRUE(xt::allclose(metrics_masked[m], metrics_subset[m]))
<< "Failure for (" << metrics[m] << ")";
}
}
TEST(DeterministTests, TestMaskingConditions)
{
std::vector<std::string> metrics =
{"RMSE", "NSE", "KGE", "KGEPRIME"};
// read in data
std::ifstream ifs;
ifs.open("./data/q_obs.csv");
xt::xtensor<double, 2> observed = xt::load_csv<int>(ifs);
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
ifs.close();
ifs.open("./data/q_prd.csv");
xt::xtensor<double, 2> predicted = xt::load_csv<double>(ifs);
ifs.close();
// generate dummy empty masks required to access next optional argument
xt::xtensor<bool, 2> masks;
// conditions on streamflow ________________________________________________
// compute scores using masking conditions on streamflow to subset whole record
xt::xtensor<std::array<char, 32>, 2> q_conditions ={
{{"q_obs{<2000,>3000}"}}
};
std::vector<xt::xarray<double>> metrics_q_conditioned =
evalhyd::evald<2>(
observed, predicted, metrics,
"none", 1, -9, masks, q_conditions
);
// compute scores using "NaN-ed" time indices where conditions on streamflow met
std::vector<xt::xarray<double>> metrics_q_preconditioned =
evalhyd::evald<2>(
xt::where((observed < 2000) | (observed > 3000), observed, NAN),
predicted,
metrics
);
// check results are identical
for (int m = 0; m < metrics.size(); m++)
{
EXPECT_TRUE(
xt::allclose(
metrics_q_conditioned[m], metrics_q_preconditioned[m]
)
) << "Failure for (" << metrics[m] << ")";
}
// conditions on temporal indices __________________________________________
// compute scores using masking conditions on time indices to subset whole record
xt::xtensor<std::array<char, 32>, 2> t_conditions = {
{{"t{0,1,2,3,4,5:97,97,98,99}"}}
};
std::vector<xt::xarray<double>> metrics_t_conditioned =
evalhyd::evald<2>(
observed, predicted, metrics,
"none", 1, -9, masks, t_conditions
);
// compute scores on already subset time series
std::vector<xt::xarray<double>> metrics_t_subset =
evalhyd::evald<2>(
xt::view(observed, xt::all(), xt::range(0, 100)),
xt::view(predicted, xt::all(), xt::range(0, 100)),
metrics
);
// check results are identical
for (int m = 0; m < metrics.size(); m++)
{
EXPECT_TRUE(
xt::allclose(
metrics_t_conditioned[m], metrics_t_subset[m]
)
) << "Failure for (" << metrics[m] << ")";
}
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
}
TEST(DeterministTests, TestMissingData)
{
std::vector<std::string> metrics =
{"RMSE", "NSE", "KGE", "KGEPRIME"};
// 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 = xt::view(
xt::load_csv<double>(ifs), xt::range(0, 5), xt::all()
);
ifs.close();
// add some missing observations artificially by assigning NaN values
xt::view(observed, xt::all(), xt::range(0, 20)) = NAN;
// add some missing predictions artificially by assigning NaN values
xt::view(observed, 0, xt::range(20, 23)) = NAN;
xt::view(observed, 1, xt::range(20, 26)) = NAN;
xt::view(observed, 2, xt::range(20, 29)) = NAN;
xt::view(observed, 3, xt::range(20, 32)) = NAN;
xt::view(observed, 4, xt::range(20, 35)) = NAN;
// compute metrics with observations containing NaN values
std::vector<xt::xarray<double>> metrics_nan =
evalhyd::evald<2>(observed, predicted, metrics);
for (int m = 0; m < metrics.size(); m++) {
for (int p = 0; p < predicted.shape(0); p++) {
// compute metrics on subset of observations and predictions (i.e.
// eliminating region with NaN in observations or predictions manually)
xt::xtensor<double, 1> obs =
xt::view(observed, 0, xt::range(20+(3*(p+1)), _));
xt::xtensor<double, 1> prd =
xt::view(predicted, p, xt::range(20+(3*(p+1)), _));
std::vector<xt::xarray<double>> metrics_sbs =
evalhyd::evald<1>(obs, prd, {metrics[m]});
// compare to check results are the same
EXPECT_TRUE(
xt::allclose(
xt::view(metrics_nan[m], p),
metrics_sbs[0]
)
) << "Failure for (" << metrics[m] << ")";
}
}
}