events.hpp 10.77 KiB
// Copyright (c) 2023, INRAE.
// Distributed under the terms of the GPL-3 Licence.
// The full licence is in the file LICENCE, distributed with this software.
#ifndef EVALHYD_DETERMINIST_EVENTS_HPP
#define EVALHYD_DETERMINIST_EVENTS_HPP
#include <xtensor/xtensor.hpp>
#include <xtensor/xview.hpp>
#include <xtensor/xmasked_view.hpp>
#include <xtensor/xmath.hpp>
namespace evalhyd
    namespace determinist
        namespace elements
            // Contingency table:
            //                  OBS
            //                Y     N
            //             +-----+-----+      a: hits
            //           Y |  a  |  b  |      b: false alarms
            //     PRD     +-----+-----+      c: misses
            //           N |  c  |  d  |      d: correct rejections
            //             +-----+-----+
            /// Determine observed realisation of threshold(s) exceedance.
            ///
            /// \param q_obs
            ///     Streamflow observations.
            ///     shape: (1, time)
            /// \param q_thr
            ///     Streamflow exceedance threshold(s).
            ///     shape: (series, thresholds)
            /// \param is_high_flow_event
            ///     Whether events correspond to being above the threshold(s).
            /// \return
            ///     Event observed outcome.
            ///     shape: (series, thresholds, time)
            template<class XD2>
            inline xt::xtensor<double, 3> calc_obs_event(
                    const XD2& q_obs,
                    const XD2& q_thr,
                    bool is_high_flow_event
                if (is_high_flow_event)
                    // observations above threshold(s)
                    return xt::view(q_obs, xt::all(), xt::newaxis(), xt::all())
                           >= xt::view(q_thr, xt::all(), xt::all(), xt::newaxis());
                else
                    // observations below threshold(s)
                    return xt::view(q_obs, xt::all(), xt::newaxis(), xt::all())
                           <= xt::view(q_thr, xt::all(), xt::all(), xt::newaxis());
            /// Determine predicted realisation of threshold(s) exceedance.
            ///
            /// \param q_prd
            ///     Streamflow predictions.
            ///     shape: (series, time)
            /// \param q_thr
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
/// Streamflow exceedance threshold(s). /// shape: (series, thresholds) /// \param is_high_flow_event /// Whether events correspond to being above the threshold(s). /// \return /// Event predicted outcome. /// shape: (series, thresholds, time) template<class XD2> inline xt::xtensor<double, 3> calc_prd_event( const XD2& q_prd, const XD2& q_thr, bool is_high_flow_event ) { if (is_high_flow_event) { // observations above threshold(s) return xt::view(q_prd, xt::all(), xt::newaxis(), xt::all()) >= xt::view(q_thr, xt::all(), xt::all(), xt::newaxis()); } else { // observations below threshold(s) return xt::view(q_prd, xt::all(), xt::newaxis(), xt::all()) <= xt::view(q_thr, xt::all(), xt::all(), xt::newaxis()); } } /// Determine hits ('a' in contingency table). /// /// \param obs_event /// Observed event outcome. /// shape: (series, thresholds, time) /// \param prd_event /// Predicted event outcome. /// shape: (series, thresholds, time) /// \return /// Hits. /// shape: (series, thresholds, time) inline xt::xtensor<double, 3> calc_ct_a( const xt::xtensor<double, 3>& obs_event, const xt::xtensor<double, 3>& prd_event ) { return xt::equal(obs_event, 1.) && xt::equal(prd_event, 1.); } /// Determine false alarms ('b' in contingency table). /// /// \param obs_event /// Observed event outcome. /// shape: (series, thresholds, time) /// \param prd_event /// Predicted event outcome. /// shape: (series, thresholds, time) /// \return /// False alarms. /// shape: (series, thresholds, time) inline xt::xtensor<double, 3> calc_ct_b( const xt::xtensor<double, 3>& obs_event, const xt::xtensor<double, 3>& prd_event ) { return xt::equal(obs_event, 0.) && xt::equal(prd_event, 1.); } /// Determine misses ('c' in contingency table). /// /// \param obs_event /// Observed event outcome.
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
/// shape: (series, thresholds, time) /// \param prd_event /// Predicted event outcome. /// shape: (series, thresholds, time) /// \return /// Misses. /// shape: (series, thresholds, time) inline xt::xtensor<double, 3> calc_ct_c( const xt::xtensor<double, 3>& obs_event, const xt::xtensor<double, 3>& prd_event ) { return xt::equal(obs_event, 1.) && xt::equal(prd_event, 0.); } /// Determine correct rejections ('d' in contingency table). /// /// \param obs_event /// Observed event outcome. /// shape: (series, thresholds, time) /// \param prd_event /// Predicted event outcome. /// shape: (series, thresholds, time) /// \return /// Correct rejections. /// shape: (series, thresholds, time) inline xt::xtensor<double, 3> calc_ct_d( const xt::xtensor<double, 3>& obs_event, const xt::xtensor<double, 3>& prd_event ) { return xt::equal(obs_event, 0.) && xt::equal(prd_event, 0.); } } namespace metrics { /// Compute the cells of the contingency table (CONT_TBL), /// i.e. 'hits', 'false alarms', 'misses', 'correct rejections', /// in this order. /// /// \param q_thr /// Streamflow exceedance threshold(s). /// shape: (series, thresholds) /// \param ct_a /// Hits for each time step. /// shape: (series, thresholds, time) /// \param ct_b /// False alarms for each time step. /// shape: (series, thresholds, time) /// \param ct_c /// Misses for each time step. /// shape: (series, thresholds, time) /// \param ct_d /// Correct rejections for each time step. /// shape: (series, thresholds, time) /// \param t_msk /// Temporal subsets of the whole record. /// shape: (series, subsets, time) /// \param b_exp /// Boostrap samples. /// shape: (samples, time slice) /// \param n_srs /// Number of prediction series. /// \param n_thr /// Number of thresholds. /// \param n_msk /// Number of temporal subsets. /// \param n_exp /// Number of bootstrap samples.
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
/// \return /// Contingency tables. /// shape: (series, subsets, samples, thresholds, cells) template<class XD2> inline xt::xtensor<double, 5> calc_CONT_TBL( const XD2& q_thr, const xt::xtensor<double, 3>& ct_a, const xt::xtensor<double, 3>& ct_b, const xt::xtensor<double, 3>& ct_c, const xt::xtensor<double, 3>& ct_d, const xt::xtensor<bool, 3>& t_msk, const std::vector<xt::xkeep_slice<int>>& b_exp, std::size_t n_srs, std::size_t n_thr, std::size_t n_msk, std::size_t n_exp ) { // initialise output variable xt::xtensor<double, 5> CONT_TBL = xt::zeros<double>({n_srs, n_msk, n_exp, n_thr, std::size_t {4}}); // compute variable one mask at a time to minimise memory imprint for (std::size_t m = 0; m < n_msk; m++) { std::size_t i = 0; for (auto cell: {ct_a, ct_b, ct_c, ct_d}) { // apply the mask // (using NaN workaround until reducers work on masked_view) auto cell_masked = xt::where( xt::view(t_msk, xt::all(), m, xt::newaxis(), xt::all()), cell, NAN ); // compute variable one sample at a time for (std::size_t e = 0; e < n_exp; e++) { // apply the bootstrap sampling auto cell_masked_sampled = xt::view(cell_masked, xt::all(), xt::all(), b_exp[e]); // calculate the mean over the time steps xt::view(CONT_TBL, xt::all(), m, e, xt::all(), i) = xt::nansum(cell_masked_sampled, -1); } i++; } } // assign NaN where thresholds were not provided (i.e. set as NaN) xt::masked_view( CONT_TBL, xt::isnan(xt::view(q_thr, xt::all(), xt::newaxis(), xt::newaxis(), xt::all(), xt::newaxis())) ) = NAN; return CONT_TBL; } } } } #endif //EVALHYD_DETERMINIST_EVENTS_HPP