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-04-28 01:25:40 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       74037 : Dimensions::Dimensions(const scipp::span<const Dim> labels,
      15       74037 :                        const scipp::span<const scipp::index> shape) {
      16       74037 :   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      214548 :   for (scipp::index i = 0; i < scipp::size(shape); ++i)
      23      140511 :     addInner(labels[i], shape[i]);
      24       74037 : }
      25             : 
      26     2035629 : Dim Dimensions::label(const scipp::index i) const { return labels()[i]; }
      27             : 
      28     7490975 : 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         240 : void Dimensions::add(const Dim label, const scipp::index size) {
      41         240 :   expect::validDim(label);
      42         240 :   expect::validExtent(size);
      43         240 :   insert_left(label, size);
      44         237 : }
      45             : 
      46             : /// Add a new dimension, which will be the innermost dimension.
      47     1455120 : void Dimensions::addInner(const Dim label, const scipp::index size) {
      48     1455120 :   expect::validDim(label);
      49     1455120 :   expect::validExtent(size);
      50     1455120 :   insert_right(label, size);
      51     1455112 : }
      52             : 
      53             : /// Return the innermost dimension. Returns Dim::Invalid if *this is empty
      54      328215 : Dim Dimensions::inner() const noexcept {
      55      328215 :   if (empty())
      56          72 :     return Dim::Invalid;
      57      328143 :   return labels().back();
      58             : }
      59             : 
      60             : Dimensions
      61        1957 : Dimensions::rename_dims(const std::vector<std::pair<Dim, Dim>> &names,
      62             :                         const bool fail_on_unknown) const {
      63        1957 :   return detail::rename_dims(*this, names, fail_on_unknown);
      64             : }
      65             : 
      66      527887 : 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     1906590 : Dimensions merge(const Dimensions &a, const Dimensions &b) {
      75     1906590 :   Dimensions out;
      76     1906590 :   auto it = b.labels().begin();
      77     1906590 :   auto end = b.labels().end();
      78     2881594 :   for (const auto &dim : a.labels()) {
      79             :     // add any labels appearing *before* dim
      80      975026 :     if (b.contains(dim)) {
      81      868654 :       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     1183721 :       while (it != end && *it != dim) {
      86      315089 :         if (!a.contains(*it))
      87        3032 :           out.addInner(*it, b[*it]);
      88      315089 :         ++it;
      89             :       }
      90             :     }
      91      975004 :     out.addInner(dim, a[dim]);
      92             :   }
      93             :   // add remaining labels appearing after last of a's labels
      94     2653896 :   while (it != end) {
      95      747328 :     if (!a.contains(*it))
      96      190753 :       out.addInner(*it, b[*it]);
      97      747328 :     ++it;
      98             :   }
      99     1906568 :   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        1571 : Dimensions intersection(const Dimensions &a, const Dimensions &b) {
     107        1571 :   Dimensions out;
     108        1571 :   Dimensions m = merge(a, b);
     109        4735 :   for (const auto &dim : m.labels()) {
     110        3164 :     if (a.contains(dim) && b.contains(dim))
     111        3133 :       out.addInner(dim, m[dim]);
     112             :   }
     113        3142 :   return out;
     114        1571 : }
     115             : 
     116             : namespace {
     117       11793 : Dimensions transpose_impl(const Dimensions &dims,
     118             :                           const scipp::span<const Dim> labels) {
     119       11793 :   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       11782 :   std::vector<scipp::index> shape(labels.size());
     123       11782 :   std::transform(labels.begin(), labels.end(), shape.begin(),
     124       27217 :                  [&dims](const auto &dim) { return dims[dim]; });
     125       23556 :   return {labels, shape};
     126       11782 : }
     127             : } // namespace
     128             : 
     129       11793 : Dimensions transpose(const Dimensions &dims,
     130             :                      const scipp::span<const Dim> labels) {
     131       11793 :   if (labels.empty()) {
     132        2099 :     std::vector<Dim> default_labels{dims.labels().rbegin(),
     133        2099 :                                     dims.labels().rend()};
     134        2099 :     return transpose_impl(dims, default_labels);
     135        2099 :   }
     136        9694 :   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         253 : Dimensions fold(const Dimensions &old_dims, const Dim from_dim,
     148             :                 const Dimensions &to_dims) {
     149         253 :   scipp::expect::contains(old_dims, from_dim);
     150         251 :   Dimensions new_dims;
     151         542 :   for (const auto &dim : old_dims.labels())
     152         291 :     if (dim != from_dim)
     153          40 :       new_dims.addInner(dim, old_dims[dim]);
     154             :     else
     155         762 :       for (const auto &lab : to_dims.labels())
     156         511 :         new_dims.addInner(lab, to_dims[lab]);
     157         251 :   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         249 :   return new_dims;
     164           2 : }
     165             : 
     166             : } // namespace scipp::core

Generated by: LCOV version 1.14