intervals.hpp 18.46 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_PROBABILIST_INTERVALS_HPP
#define EVALHYD_PROBABILIST_INTERVALS_HPP
#include <limits>
#include <xtensor/xtensor.hpp>
#include <xtensor/xview.hpp>
#include <xtensor/xindex_view.hpp>
#include <xtensor/xsort.hpp>
namespace evalhyd
    namespace probabilist
        namespace elements
            /// Compute the bounds of the predictive intervals by computing
            /// the quantiles of the predictive distribution corresponding
            /// to the confidence intervals.
            ///
            /// \param q_prd
            ///     Streamflow predictions.
            ///     shape: (sites, lead times, members, time)
            /// \param c_lvl
            ///     Confidence levels for the predictive intervals.
            ///     shape: (intervals,)
            /// \param n_sit
            ///     Number of sites.
            /// \param n_ldt
            ///     Number of lead times.
            /// \param n_itv
            ///     Number of predictive intervals.
            /// \param n_tim
            ///     Number of time steps.
            /// \return
            ///     Bounds of the predictive intervals.
            ///     shape: (sites, lead times, intervals, bounds, time)
            template <class XD4>
            inline xt::xtensor<double, 5> calc_itv_bnds(
                    const XD4& q_prd,
                    const xt::xtensor<double, 1>& c_lvl,
                    const xt::xtensor<double, 1>& q_lvl,
                    std::size_t n_sit,
                    std::size_t n_ldt,
                    std::size_t n_itv,
                    std::size_t n_tim
                xt::xtensor<double, 5> itv_bnds =
                        xt::zeros<double>({n_sit, n_ldt, n_itv, std::size_t {2}, n_tim});
                // determine quantiles forming the predictive intervals
                // from the confidence levels
                xt::xtensor<double, 2> quantiles =
                        xt::zeros<double>({n_itv, std::size_t {2}});
                xt::col(quantiles, 0) = 0.5 - c_lvl / 200.;
                xt::col(quantiles, 1) = 0.5 + c_lvl / 200.;
                // get or compute predictive interval bounds from quantiles
                if (q_lvl.size() > 0)
                    for (std::size_t i = 0; i < n_itv; i++)
                    auto a = xt::broadcast(xt::view(quantiles, i), std::vector<std::size_t>({q_lvl.size(), 2}));
                    auto b = xt::broadcast(q_lvl, std::vector<std::size_t>({2, q_lvl.size()}));
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
auto res = xt::where(xt::equal(a, xt::transpose(b))); if (res.size() != 2) { throw std::runtime_error( "interval-based metric requested, " "but *c_lvl* not matching *q_lvl" ); } else { xt::view(itv_bnds, xt::all(), xt::all(), i, 0, xt::all()) = xt::view(q_prd, xt::all(), xt::all(), std::min(res[0][1], res[1][1]), xt::all()); xt::view(itv_bnds, xt::all(), xt::all(), i, 1, xt::all()) = xt::view(q_prd, xt::all(), xt::all(), std::max(res[0][1], res[1][1]), xt::all()); } } } else { for (std::size_t i = 0; i < n_itv; i++) { auto q = xt::quantile(q_prd, xt::view(quantiles, i), 2); xt::view(itv_bnds, xt::all(), xt::all(), i, 0, xt::all()) = xt::view(q, 0); xt::view(itv_bnds, xt::all(), xt::all(), i, 1, xt::all()) = xt::view(q, 1); } } return itv_bnds; } /// Determine whether the observations are inside the predictive /// intervals for each time step. /// /// \param q_obs /// Streamflow predictions. /// shape: (sites, time) /// \param itv_bnds /// Bounds of the predictive intervals. /// shape: (sites, lead times, intervals, bounds, time) /// \return /// Boolean-like tensor evaluating to true where observations /// are inside the predictive intervals. /// shape: (sites, lead times, intervals, time) template <class XD2> inline xt::xtensor<double, 4> calc_obs_in_itv( const XD2& q_obs, const xt::xtensor<double, 5>& itv_bnds ) { // notations below follow Gneiting and Raftery (2007), sect 6.2 // https://doi.org/10.1198/016214506000001437 auto x = xt::view(q_obs, xt::all(), xt::newaxis(), xt::newaxis(), xt::all()); auto l = xt::view(itv_bnds, xt::all(), xt::all(), xt::all(), 0, xt::all()); auto u = xt::view(itv_bnds, xt::all(), xt::all(), xt::all(), 1, xt::all()); return ((x >= l) && (x <= u)); } /// Compute the width of the predictive intervals for each time step. /// /// \param itv_bnds /// Bounds of the predictive intervals. /// shape: (sites, lead times, intervals, bounds, time) /// \return /// Interval scores for each time step. /// shape: (sites, lead times, intervals, time) inline xt::xtensor<double, 4> calc_itv_width(