LCOV - code coverage report
Current view: top level - variable - rebin.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 74 88 84.1 %
Date: 2024-11-17 01:47:58 Functions: 11 23 47.8 %

          Line data    Source code
       1             : // SPDX-License-Identifier: BSD-3-Clause
       2             : // Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
       3             : /// @file
       4             : /// @author Simon Heybrock, Igor Gudich
       5             : #include "scipp/core/element/rebin.h"
       6             : #include "scipp/core/parallel.h"
       7             : #include "scipp/units/except.h"
       8             : #include "scipp/variable/arithmetic.h"
       9             : #include "scipp/variable/astype.h"
      10             : #include "scipp/variable/rebin.h"
      11             : #include "scipp/variable/reduction.h"
      12             : #include "scipp/variable/shape.h"
      13             : #include "scipp/variable/transform_subspan.h"
      14             : #include "scipp/variable/util.h"
      15             : 
      16             : using namespace scipp::core::element;
      17             : 
      18             : namespace scipp::variable {
      19             : 
      20             : template <typename T, class Less>
      21          40 : void rebin_non_inner(const Dim dim, const Variable &oldT,
      22             :                      // cppcheck-suppress constParameter # bug in cppcheck
      23             :                      Variable &newT, const Variable &oldCoord,
      24             :                      const Variable &newCoord) {
      25          40 :   if (oldCoord.ndim() != 1 || newCoord.ndim() != 1)
      26           0 :     throw std::invalid_argument(
      27             :         "Internal error in rebin, this should be unreachable.");
      28          40 :   const auto oldSize = oldT.dims()[dim];
      29          40 :   const auto newSize = newT.dims()[dim];
      30             : 
      31          40 :   const auto *xold = oldCoord.values<T>().data();
      32          40 :   const auto *xnew = newCoord.values<T>().data();
      33             : 
      34         392 :   auto add_from_bin = [&](auto &&slice, const auto xn_low, const auto xn_high,
      35             :                           const scipp::index iold) {
      36          88 :     auto xo_low = xold[iold];
      37          88 :     auto xo_high = xold[iold + 1];
      38             :     // delta is the overlap of the bins on the x axis
      39         176 :     const auto delta = std::abs(std::min<double>(xn_high, xo_high, Less{}) -
      40          88 :                                 std::max<double>(xn_low, xo_low, Less{}));
      41          88 :     const auto owidth = std::abs(xo_high - xo_low);
      42          88 :     slice += oldT.slice({dim, iold}) *
      43         176 :              ((delta / owidth) * (slice.unit() / oldT.unit()));
      44             :   };
      45         373 :   auto accumulate_bin = [&](auto &&slice, const auto xn_low,
      46             :                             const auto xn_high) {
      47          56 :     scipp::index begin =
      48          56 :         std::upper_bound(xold, xold + oldSize + 1, xn_low, Less{}) - xold;
      49          56 :     scipp::index end =
      50          56 :         std::upper_bound(xold, xold + oldSize + 1, xn_high, Less{}) - xold;
      51          56 :     if (begin == oldSize + 1 || end == 0)
      52           0 :       return;
      53          56 :     begin = std::max(scipp::index(0), begin - 1);
      54          56 :     add_from_bin(slice, xn_low, xn_high, begin);
      55          56 :     if (begin + 1 < end - 1)
      56          27 :       sum_into(slice, oldT.slice({dim, begin + 1, end - 1}));
      57          56 :     if (begin != end - 1 && end < oldSize + 1)
      58          32 :       add_from_bin(slice, xn_low, xn_high, end - 1);
      59             :   };
      60          80 :   auto accumulate_bins = [&](const auto &range) {
      61          96 :     for (scipp::index inew = range.begin(); inew < range.end(); ++inew) {
      62          56 :       auto xn_low = xnew[inew];
      63          56 :       auto xn_high = xnew[inew + 1];
      64          56 :       accumulate_bin(newT.slice({dim, inew}), xn_low, xn_high);
      65             :     }
      66             :   };
      67          40 :   core::parallel::parallel_for(core::parallel::blocked_range(0, newSize),
      68             :                                accumulate_bins);
      69          40 : }
      70             : 
      71             : namespace {
      72             : template <class Out, class OutEdge, class In, class InEdge>
      73             : using args = std::tuple<scipp::span<Out>, scipp::span<const OutEdge>,
      74             :                         scipp::span<const In>, scipp::span<const InEdge>>;
      75             : 
      76             : struct Greater {
      77             :   template <class A, class B>
      78         402 :   constexpr bool operator()(const A a, const B b) const noexcept {
      79         402 :     return a > b;
      80             :   }
      81             : };
      82             : 
      83             : struct Less {
      84             :   template <class A, class B>
      85        1545 :   constexpr bool operator()(const A a, const B b) const noexcept {
      86        1545 :     return a < b;
      87             :   }
      88             : };
      89             : 
      90             : } // namespace
      91             : 
      92          96 : Variable rebin(const Variable &var, const Dim dim, const Variable &oldCoord,
      93             :                const Variable &newCoord) {
      94             :   // The code branch dealing with non-stride-1 data cannot handle non-1D edges.
      95             :   // This is likely a rare case in practice so a slow transpose of input and
      96             :   // output should be sufficient for now.
      97          96 :   if (var.stride(dim) != 1 && (oldCoord.ndim() != 1 || newCoord.ndim() != 1))
      98             :     // We *copy* the transpose to ensure that memory order of dims matches input
      99             :     return copy(
     100           4 :         transpose(rebin(as_contiguous(var, dim), dim, oldCoord, newCoord),
     101           4 :                   var.dims().labels()));
     102             :   // Rebin could also implemented for count-densities. However, it may be better
     103             :   // to avoid this since it increases complexity. Instead, densities could
     104             :   // always be computed on-the-fly for visualization, if required.
     105          94 :   if (!is_edges(var.dims(), oldCoord.dims(), dim))
     106           0 :     throw except::BinEdgeError(
     107           0 :         "The input does not have coordinates with bin-edges.");
     108             : 
     109          94 :   if (is_bins(var))
     110           1 :     throw except::TypeError("The input variable cannot be binned data. Use "
     111           2 :                             "`bin` or `histogram` instead of `rebin`.");
     112             : 
     113             :   using transform_args = std::tuple<
     114             :       args<double, double, int64_t, double>,
     115             :       args<double, double, int32_t, double>,
     116             :       args<double, core::time_point, double, core::time_point>,
     117             :       args<float, core::time_point, float, core::time_point>,
     118             :       args<double, core::time_point, int64_t, core::time_point>,
     119             :       args<double, core::time_point, int32_t, core::time_point>,
     120             :       args<double, core::time_point, bool, core::time_point>,
     121             :       args<double, double, double, double>, args<float, float, float, float>,
     122             :       args<float, double, float, double>, args<float, float, float, double>,
     123             :       args<double, double, bool, double>>;
     124             : 
     125         157 :   const bool ascending = allsorted(oldCoord, dim, SortOrder::Ascending) &&
     126          64 :                          allsorted(newCoord, dim, SortOrder::Ascending);
     127         122 :   if (!ascending && !(allsorted(oldCoord, dim, SortOrder::Descending) &&
     128          29 :                       allsorted(newCoord, dim, SortOrder::Descending)))
     129           0 :     throw except::BinEdgeError(
     130           0 :         "Rebin: The old or new bin edges are not sorted.");
     131         182 :   const auto out_type = (is_int(var.dtype()) || var.dtype() == dtype<bool>)
     132          93 :                             ? dtype<double>
     133          93 :                             : var.dtype();
     134             :   // Both code branches below require stride 1 for input and output edges.
     135          93 :   const auto oldEdges = as_contiguous(oldCoord, dim);
     136          93 :   const auto newEdges = as_contiguous(newCoord, dim);
     137          93 :   Variable rebinned;
     138          93 :   if (var.stride(dim) == 1) {
     139          53 :     if (ascending) {
     140          76 :       rebinned = transform_subspan<transform_args>(
     141          38 :           out_type, dim, newEdges.dims()[dim] - 1, newEdges, var, oldEdges,
     142          38 :           core::element::rebin<Less>, "rebin");
     143             :     } else {
     144          30 :       rebinned = transform_subspan<transform_args>(
     145          15 :           out_type, dim, newEdges.dims()[dim] - 1, newEdges, var, oldEdges,
     146          15 :           core::element::rebin<Greater>, "rebin");
     147             :     }
     148             :   } else {
     149          40 :     auto dims = var.dims();
     150          40 :     dims.resize(dim, newEdges.dims()[dim] - 1);
     151          40 :     rebinned = Variable(astype(Variable(var, Dimensions{}), out_type), dims);
     152          40 :     if (newEdges.dims().ndim() > 1)
     153           0 :       throw std::runtime_error(
     154           0 :           "Not inner rebin works only for 1d coordinates for now.");
     155          40 :     if (oldEdges.dtype() == dtype<double>) {
     156          40 :       if (ascending)
     157          26 :         rebin_non_inner<double, Less>(dim, var, rebinned, oldEdges, newEdges);
     158             :       else
     159          14 :         rebin_non_inner<double, Greater>(dim, var, rebinned, oldEdges,
     160             :                                          newEdges);
     161           0 :     } else if (oldEdges.dtype() == dtype<float>) {
     162           0 :       if (ascending)
     163           0 :         rebin_non_inner<float, Less>(dim, var, rebinned, oldEdges, newEdges);
     164             :       else
     165           0 :         rebin_non_inner<float, Greater>(dim, var, rebinned, oldEdges, newEdges);
     166             :     } else {
     167           0 :       throw except::TypeError("Rebinning is possible only for coords of types "
     168           0 :                               "`float64` or `float32`.");
     169             :     }
     170          40 :   }
     171             :   // If rebinned dimension has stride 1 but is not an inner dimension then we
     172             :   // need to transpose the output of transform_subspan to retain the input
     173             :   // dimension order.
     174          93 :   return transpose(rebinned, var.dims().labels());
     175          93 : }
     176             : 
     177             : } // namespace scipp::variable

Generated by: LCOV version 1.14