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