LCOV - code coverage report
Current view: top level - core - dimensions.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 91 95 95.8 %
Date: 2024-11-17 01:47:58 Functions: 15 15 100.0 %

          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
       5             : #include <algorithm>
       6             : #include <numeric>
       7             : 
       8             : #include "rename.h"
       9             : #include "scipp/core/dimensions.h"
      10             : #include "scipp/core/except.h"
      11             : #include "scipp/core/sizes.h"
      12             : 
      13             : namespace scipp::core {
      14       75456 : Dimensions::Dimensions(const scipp::span<const Dim> labels,
      15       75456 :                        const scipp::span<const scipp::index> shape) {
      16       75456 :   if (labels.size() != shape.size())
      17           0 :     throw except::DimensionError(
      18             :         "Constructing Dimensions: Number of dimensions "
      19           0 :         "labels (" +
      20           0 :         std::to_string(labels.size()) + ") does not match shape size (" +
      21           0 :         std::to_string(shape.size()) + ").");
      22      218811 :   for (scipp::index i = 0; i < scipp::size(shape); ++i)
      23      143355 :     addInner(labels[i], shape[i]);
      24       75456 : }
      25             : 
      26     2104519 : Dim Dimensions::label(const scipp::index i) const { return labels()[i]; }
      27             : 
      28     7569992 : scipp::index Dimensions::size(const scipp::index i) const { return shape()[i]; }
      29             : 
      30             : /// Return the offset of elements along this dimension in a multi-dimensional
      31             : /// array defined by this.
      32         118 : scipp::index Dimensions::offset(const Dim label) const {
      33         118 :   scipp::index offset{1};
      34         158 :   for (int32_t i = index(label) + 1; i < ndim(); ++i)
      35          40 :     offset *= shape()[i];
      36         118 :   return offset;
      37             : }
      38             : 
      39             : /// Add a new dimension, which will be the outermost dimension.
      40         245 : void Dimensions::add(const Dim label, const scipp::index size) {
      41         245 :   expect::validDim(label);
      42         245 :   expect::validExtent(size);
      43         245 :   insert_left(label, size);
      44         242 : }
      45             : 
      46             : /// Add a new dimension, which will be the innermost dimension.
      47     1501593 : void Dimensions::addInner(const Dim label, const scipp::index size) {
      48     1501593 :   expect::validDim(label);
      49     1501593 :   expect::validExtent(size);
      50     1501593 :   insert_right(label, size);
      51     1501585 : }
      52             : 
      53             : /// Return the innermost dimension. Returns Dim::Invalid if *this is empty
      54      330376 : Dim Dimensions::inner() const noexcept {
      55      330376 :   if (empty())
      56          72 :     return Dim::Invalid;
      57      330304 :   return labels().back();
      58             : }
      59             : 
      60             : Dimensions
      61        1990 : Dimensions::rename_dims(const std::vector<std::pair<Dim, Dim>> &names,
      62             :                         const bool fail_on_unknown) const {
      63        1990 :   return detail::rename_dims(*this, names, fail_on_unknown);
      64             : }
      65             : 
      66      565915 : Dimensions merge(const Dimensions &a) { return a; }
      67             : 
      68             : /// Return the direct sum, i.e., the combination of dimensions in a and b.
      69             : ///
      70             : /// Throws if there is a mismatching dimension extent.
      71             : /// The implementation "favors" the order of the first argument if both
      72             : /// inputs have the same number of dimension. Transposing is avoided where
      73             : /// possible, which is crucial for accumulate performance.
      74     1942370 : Dimensions merge(const Dimensions &a, const Dimensions &b) {
      75     1942370 :   Dimensions out;
      76     1942370 :   auto it = b.labels().begin();
      77     1942370 :   auto end = b.labels().end();
      78     2945145 :   for (const auto &dim : a.labels()) {
      79             :     // add any labels appearing *before* dim
      80     1002797 :     if (b.contains(dim)) {
      81      890554 :       if (a[dim] != b[dim])
      82          22 :         throw except::DimensionError(
      83          22 :             "Cannot merge dimensions with mismatching extent in '" +
      84          66 :             to_string(dim) + "': " + to_string(a) + " and " + to_string(b));
      85     1216041 :       while (it != end && *it != dim) {
      86      325509 :         if (!a.contains(*it))
      87        3473 :           out.addInner(*it, b[*it]);
      88      325509 :         ++it;
      89             :       }
      90             :     }
      91     1002775 :     out.addInner(dim, a[dim]);
      92             :   }
      93             :   // add remaining labels appearing after last of a's labels
      94     2712409 :   while (it != end) {
      95      770061 :     if (!a.contains(*it))
      96      201565 :       out.addInner(*it, b[*it]);
      97      770061 :     ++it;
      98             :   }
      99     1942348 :   return out;
     100          22 : }
     101             : 
     102             : /// Return the dimensions contained in both a and b (dimension order is not
     103             : /// checked).
     104             : /// The convention is the same as for merge: we favor the dimension order in a
     105             : /// for dimensions found both in a and b.
     106        1711 : Dimensions intersection(const Dimensions &a, const Dimensions &b) {
     107        1711 :   Dimensions out;
     108        1711 :   Dimensions m = merge(a, b);
     109        5186 :   for (const auto &dim : m.labels()) {
     110        3475 :     if (a.contains(dim) && b.contains(dim))
     111        3444 :       out.addInner(dim, m[dim]);
     112             :   }
     113        3422 :   return out;
     114        1711 : }
     115             : 
     116             : namespace {
     117       13123 : Dimensions transpose_impl(const Dimensions &dims,
     118             :                           const scipp::span<const Dim> labels) {
     119       13123 :   if (scipp::size(labels) != dims.ndim())
     120          11 :     throw except::DimensionError("Cannot transpose: Requested new dimension "
     121          22 :                                  "order contains different number of labels.");
     122       13112 :   std::vector<scipp::index> shape(labels.size());
     123       13112 :   std::transform(labels.begin(), labels.end(), shape.begin(),
     124       29945 :                  [&dims](const auto &dim) { return dims[dim]; });
     125       26216 :   return {labels, shape};
     126       13112 : }
     127             : } // namespace
     128             : 
     129       13123 : Dimensions transpose(const Dimensions &dims,
     130             :                      const scipp::span<const Dim> labels) {
     131       13123 :   if (labels.empty()) {
     132        2263 :     std::vector<Dim> default_labels{dims.labels().rbegin(),
     133        2263 :                                     dims.labels().rend()};
     134        2263 :     return transpose_impl(dims, default_labels);
     135        2263 :   }
     136       10860 :   return transpose_impl(dims, labels);
     137             : }
     138             : 
     139             : /// Fold one dim into multiple dims
     140             : ///
     141             : /// Go through the old dims and:
     142             : /// - if the dim does not equal the dim that is being stacked, copy dim/shape
     143             : /// - if the dim equals the dim to be stacked, replace by stack of new dims
     144             : ///
     145             : /// Note that addInner will protect against inserting new dims that already
     146             : /// exist in the old dims.
     147         265 : Dimensions fold(const Dimensions &old_dims, const Dim from_dim,
     148             :                 const Dimensions &to_dims) {
     149         265 :   scipp::expect::contains(old_dims, from_dim);
     150         263 :   Dimensions new_dims;
     151         566 :   for (const auto &dim : old_dims.labels())
     152         303 :     if (dim != from_dim)
     153          40 :       new_dims.addInner(dim, old_dims[dim]);
     154             :     else
     155         819 :       for (const auto &lab : to_dims.labels())
     156         556 :         new_dims.addInner(lab, to_dims[lab]);
     157         263 :   if (old_dims.volume() != new_dims.volume())
     158           2 :     throw except::DimensionError(
     159           4 :         "Sizes " + to_string(to_dims) +
     160           4 :         " provided to `fold` not compatible with length '" +
     161           8 :         std::to_string(old_dims[from_dim]) + "' of dimension '" +
     162           8 :         from_dim.name() + "' being folded.");
     163         261 :   return new_dims;
     164           2 : }
     165             : 
     166             : } // namespace scipp::core

Generated by: LCOV version 1.14