diff --git a/include/evalhyd/deterministic.hpp b/include/evalhyd/deterministic.hpp index 4691796bae9fa956f876114edd2e65a0de1755a0..a7f62771908f413877afa5c45fbf4ad99d567977 100644 --- a/include/evalhyd/deterministic.hpp +++ b/include/evalhyd/deterministic.hpp @@ -10,29 +10,25 @@ namespace evalhyd /// Compute the Nash-Sutcliffe Efficiency (NSE). /// /// If multi-dimensional arrays are provided, the arrays of simulations - /// and observations must feature the same number of dimensions, and - /// they must be broadcastable. The optional parameter *axis* may be used - /// to indicate which axis corresponds to the temporal dimension on which - /// to perform the reductions. The temporal dimension should not be one - /// to be broadcast. + /// and observations must feature the same number of dimensions, they must + /// be broadcastable, and their temporal dimensions must be along their + /// last axis. /// /// \tparam A: /// The type of data structures (e.g. `xt::xarray`, `xt::xtensor`). /// \param [in] sim: /// Array of streamflow simulations. + /// shape: (..., time) /// \param [in] obs: /// Array of streamflow observations. - /// \param [in] axis (optional): - /// Array axis along which the temporal dimension is for both *sim* - /// arrays are multi-dimensional. If not provided, set to default - /// value 0. + /// shape: (1, time) /// \return /// Array of computed efficiencies. + /// shape: (...) template <class A> inline A nse( const xt::xexpression<A>& sim, - const xt::xexpression<A>& obs, - int axis = 0 + const xt::xexpression<A>& obs ) { const A& q_sim = sim.derived_cast(); @@ -46,15 +42,12 @@ namespace evalhyd ); } - // turn axis into a vector - std::vector<int> axes = { axis }; - // compute average observed flow - A q_avg = xt::mean(q_obs, axes, xt::keep_dims); + 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_sim), axes, xt::keep_dims); - A f_den = xt::sum(xt::square(q_obs - q_avg), axes, xt::keep_dims); + A f_num = xt::sum(xt::square(q_obs - q_sim), -1, xt::keep_dims); + A f_den = xt::sum(xt::square(q_obs - q_avg), -1, xt::keep_dims); // return computed NSE return 1 - (f_num / f_den); diff --git a/include/evalhyd/probabilistic.hpp b/include/evalhyd/probabilistic.hpp index 350c82c05615dfd9662b29b9225f0eefd291c726..881bfeb10517290257e79f29d6824f971837b07c 100644 --- a/include/evalhyd/probabilistic.hpp +++ b/include/evalhyd/probabilistic.hpp @@ -14,19 +14,10 @@ namespace evalhyd { namespace detail { - /// check whether the axis is compatible with 2D arrays - void check_axis(int axis) - { - // check that axis is a valid index for a 2D array - if ((axis != 0) && (axis != 1)) - { - throw std::out_of_range( - "array axis must be 0 or 1 for 2D arrays" - ); - } - } - - /// determine whether exceedance event occurred + // determine whether exceedance event has occurred + // q_obs shape: (1, time) + // q_thr shape: (thresholds,) + // returned shape: (thresholds, time) xt::xtensor<double, 2> event_observed_outcome( const xt::xtensor<double, 2>& q_obs, const xt::xtensor<double, 1>& q_thr, @@ -43,11 +34,13 @@ namespace evalhyd } } - /// determine forecast probability of event to occur + // determine forecast probability of event to occur + // q_obs shape: (members, time) + // q_thr shape: (thresholds,) + // returned shape: (thresholds, members, time) xt::xtensor<double, 2> event_probability_forecast( const xt::xtensor<double, 2>& q_frc, const xt::xtensor<double, 1>& q_thr, - int axis, bool above ) { @@ -57,14 +50,13 @@ namespace evalhyd xt::xtensor<double, 3> e_frc = f(q_frc, q_thr); // calculate how many members have exceeded threshold(s) - int axis_mbr = (axis == 0) ? 1 : 0; - xt::xtensor<double, 2> n_frc = xt::sum(e_frc, axis_mbr); + xt::xtensor<double, 2> n_frc = xt::sum(e_frc, 1); // determine correspondence between number of exceeding members // and forecast probabilities of exceedance // /!\ probability calculation dividing by n (the number of // members), not n+1 (the number of ranks) like other metrics - std::size_t n_mbr = q_frc.shape(axis_mbr); + std::size_t n_mbr = q_frc.shape(0); xt::xtensor<double, 2> p_frc = n_frc / n_mbr; return p_frc; @@ -75,14 +67,13 @@ namespace evalhyd /// /// \param [in] q_obs: /// 2D array of streamflow observations. + /// shape: (1, time) /// \param [in] q_frc: /// 2D array of streamflow forecasts. + /// shape: (members, time) /// \param [in] q_thr: /// 1D array of streamflow exceedance threshold(s). - /// \param [in] axis (optional): - /// Array axis along which the temporal dimension is for both - /// *frc* and *obs* (either 0 or 1). If not provided, set to - /// default value 0. + /// shape: (thresholds,) /// \param [in] above (optional): /// Boolean to indicate whether exceedance corresponds to being greater /// (above) the thresholds or being lower than the thresholds (below). @@ -90,39 +81,36 @@ namespace evalhyd /// default value True. /// \return /// 1D array of Brier Score for each threshold. + /// shape: (thresholds,) xt::xtensor<double, 1> bs( const xt::xtensor<double, 2>& q_obs, const xt::xtensor<double, 2>& q_frc, const xt::xtensor<double, 1>& q_thr, - int axis = 0, bool above = true ) { - detail::check_axis(axis); - // get observed event and forecast probability of event xt::xtensor<double, 2> p_obs = detail::event_observed_outcome(q_obs, q_thr, above); xt::xtensor<double, 2> p_frc = - detail::event_probability_forecast(q_frc, q_thr, axis, above); + detail::event_probability_forecast(q_frc, q_thr, above); // return computed BS - return xt::mean(xt::square(p_obs - p_frc), std::vector<int> { axis }); + return xt::mean(xt::square(p_obs - p_frc), -1); } /// Compute the Brier Skill Score (BSS). /// /// \param [in] q_obs: /// 2D array of streamflow observations. + /// shape: (1, time) /// \param [in] q_frc: /// 2D array of streamflow forecasts. + /// shape: (members, time) /// \param [in] q_thr: /// 1D array of streamflow exceedance threshold(s). - /// \param [in] axis (optional): - /// Array axis along which the temporal dimension is for both - /// *frc* and *obs* (either 0 or 1). If not provided, set to - /// default value 0. + /// shape: (thresholds,) /// \param [in] above (optional): /// Boolean to indicate whether exceedance corresponds to being greater /// (above) the thresholds or being lower than the thresholds (below). @@ -130,11 +118,11 @@ namespace evalhyd /// default value True. /// \return /// 1D array of Brier Skill Score for each threshold. + /// shape: (thresholds,) xt::xtensor<double, 1> bss( const xt::xtensor<double, 2>& q_obs, const xt::xtensor<double, 2>& q_frc, const xt::xtensor<double, 1>& q_thr, - int axis = 0, bool above = true ) { @@ -143,16 +131,15 @@ namespace evalhyd detail::event_observed_outcome(q_obs, q_thr, above); // calculate mean observed realisation of threshold(s) exceedance - xt::xtensor<double, 1> p_obs_mean = - xt::mean(p_obs, std::vector<int> { axis }); + xt::xtensor<double, 2> p_obs_mean = xt::mean(p_obs, -1, xt::keep_dims); // calculate reference Brier Score xt::xtensor<double, 1> bs_ref = xt::mean( - xt::square(p_obs - p_obs_mean), std::vector<int> { axis } + xt::square(p_obs - p_obs_mean), -1 ); // return computed BSS - return 1 - (bs(q_obs, q_frc, q_thr, axis) / bs_ref); + return 1 - (bs(q_obs, q_frc, q_thr) / bs_ref); } } diff --git a/include/evalhyd/utils.hpp b/include/evalhyd/utils.hpp index aad268c2f05d13cd018379cf3523a6d8cf158efc..3a83c6550218cc7d86ad49d5528a09b2bfadd095 100644 --- a/include/evalhyd/utils.hpp +++ b/include/evalhyd/utils.hpp @@ -22,25 +22,21 @@ namespace evalhyd /// the threshold is exceeded, and zeros otherwise. /// /// \param [in] q: - /// 2D array of streamflow data + /// 2D array of streamflow data. + /// shape: (1+, time) /// \param [in] thr: - /// 1D array of streamflow threshold(s) + /// 1D array of streamflow threshold(s). + /// shape: (thresholds,) /// \return - /// 3D array of ones and zeros + /// 3D array of ones and zeros. + /// shape: (thresholds, 1+, time) xt::xtensor<double, 3> is_above_threshold( const xt::xtensor<double, 2>& q, const xt::xtensor<double, 1>& thr ) { - // add one dimension to the streamflow data - auto new_shape = {q.shape(0), q.shape(1), thr.size()}; - auto q2 = xt::broadcast( - xt::view(q, xt::all(), xt::all(), xt::newaxis()), - new_shape - ); - // return a boolean-like array - return q2 >= thr; + return q >= xt::view(thr, xt::all(), xt::newaxis(), xt::newaxis()); } /// Determine whether flows are strictly lower than given threshold(s) @@ -54,25 +50,21 @@ namespace evalhyd /// the threshold is strictly not exceeded, and zeros otherwise. /// /// \param [in] q: - /// 2D array of streamflow data + /// 2D array of streamflow data. + /// shape: (1+, time) /// \param [in] thr: - /// 1D array of streamflow threshold(s) + /// 1D array of streamflow threshold(s). + /// shape: (thresholds,) /// \return - /// 3D array of ones and zeros + /// 3D array of ones and zeros. + /// shape: (thresholds, 1+, time) xt::xtensor<double, 3> is_below_threshold( const xt::xtensor<double, 2>& q, const xt::xtensor<double, 1>& thr ) { - // add one dimension to the streamflow data - auto new_shape = {q.shape(0), q.shape(1), thr.size()}; - auto q2 = xt::broadcast( - xt::view(q, xt::all(), xt::all(), xt::newaxis()), - new_shape - ); - // return a boolean-like array - return q2 < thr; + return q < xt::view(thr, xt::all(), xt::newaxis(), xt::newaxis()); } } }