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 "scipp/variable/arithmetic.h"
6 : #include "scipp/variable/bin_array_model.h"
7 : #include "scipp/variable/cumulative.h"
8 : #include "scipp/variable/shape.h"
9 : #include "scipp/variable/structure_array_model.h"
10 : #include "scipp/variable/variable.tcc"
11 : #include "scipp/variable/variable_factory.h"
12 :
13 : namespace scipp::variable {
14 :
15 : // Avoid RTTI issues across DSO boundaries on OSX. Use of requireT and call of
16 : // Variable::values<T>() causes extra instantiations and tests failing with
17 : // std::bad_cast in dataset module. Helper functions have been moved to .cpp to
18 : // avoid some of these (see namespace bin_array_variable_detail below, but this
19 : // particular one is hard to avoid. The `extern template` declaration avoids the
20 : // instantiation in a different DSO.
21 : extern template class StructureArrayModel<scipp::index_pair, scipp::index>;
22 :
23 : template <> struct model<scipp::index_pair> {
24 : using type = StructureArrayModel<scipp::index_pair, scipp::index>;
25 : };
26 :
27 : namespace bin_array_variable_detail {
28 : SCIPP_VARIABLE_EXPORT std::tuple<Variable, scipp::index>
29 : contiguous_indices(const Variable &parent, const Dimensions &dims);
30 : SCIPP_VARIABLE_EXPORT const scipp::index_pair *
31 : index_pair_data(const Variable &indices);
32 : SCIPP_VARIABLE_EXPORT scipp::index size_from_end_index(const Variable &end);
33 : SCIPP_VARIABLE_EXPORT VariableConceptHandle
34 : zero_indices(const scipp::index size);
35 : } // namespace bin_array_variable_detail
36 :
37 1 : template <class T> std::tuple<Variable, Dim, T> Variable::to_constituents() {
38 1 : Variable tmp;
39 1 : std::swap(*this, tmp);
40 : // cppcheck-suppress constVariable # Deduced by auto.
41 1 : auto &model = requireT<BinArrayModel<T>>(tmp.data());
42 2 : return {tmp.bin_indices(), model.bin_dim(), std::move(model.buffer())};
43 1 : }
44 :
45 873479 : template <class T> std::tuple<Variable, Dim, T> Variable::constituents() const {
46 : // cppcheck-suppress constVariable # Deduced by auto.
47 873479 : auto &model = requireT<const BinArrayModel<T>>(data());
48 1746956 : return {bin_indices(), model.bin_dim(), model.buffer()};
49 : }
50 :
51 41589 : template <class T> std::tuple<Variable, Dim, T> Variable::constituents() {
52 : // cppcheck-suppress constVariable # Deduced by auto.
53 41589 : auto &model = requireT<BinArrayModel<T>>(data());
54 83178 : return {bin_indices(), model.bin_dim(), model.buffer()};
55 : }
56 :
57 119143 : template <class T> const T &Variable::bin_buffer() const {
58 119143 : return requireT<const BinArrayModel<T>>(data()).buffer();
59 : }
60 :
61 8459 : template <class T> T &Variable::bin_buffer() {
62 8459 : return requireT<BinArrayModel<T>>(data()).buffer();
63 : }
64 :
65 : template <class T> class BinVariableMakerCommon : public AbstractVariableMaker {
66 : public:
67 417676 : [[nodiscard]] bool is_bins() const override { return true; }
68 4476 : [[nodiscard]] Variable empty_like(const Variable &prototype,
69 : const std::optional<Dimensions> &shape,
70 : const Variable &sizes) const override {
71 4476 : if (shape)
72 0 : throw except::TypeError(
73 : "Cannot specify shape in `empty_like` for prototype with bins, shape "
74 : "must be given by shape of `sizes`.");
75 4476 : const auto [indices, dim, buf] = prototype.constituents<T>();
76 4476 : auto sizes_ = sizes;
77 4476 : if (!sizes.is_valid()) {
78 4443 : const auto &[begin, end] = unzip(indices);
79 4443 : sizes_ = end - begin;
80 4443 : }
81 4476 : const auto end = cumsum(sizes_);
82 4476 : const auto begin = end - sizes_;
83 4476 : const auto size = bin_array_variable_detail::size_from_end_index(end);
84 : return make_bins_no_validate(zip(begin, end), dim,
85 8952 : resize_default_init(buf, dim, size));
86 4476 : }
87 : };
88 :
89 : template <class T> class BinVariableMaker : public BinVariableMakerCommon<T> {
90 : private:
91 : const Variable &
92 15429 : bin_parent(const typename AbstractVariableMaker::parent_list &parents) const {
93 33347 : constexpr auto is_bins = [](const Variable &x) {
94 33347 : return x.dtype() == dtype<bucket<T>>;
95 : };
96 15429 : const auto count = std::count_if(parents.begin(), parents.end(), is_bins);
97 15429 : if (count == 0)
98 0 : throw except::BinnedDataError("Bin cannot have zero parents");
99 44 : if (!std::is_same_v<T, Variable> && (count > 1))
100 1 : throw except::BinnedDataError(
101 : "Binary operations such as '+' with binned data are only supported "
102 : "with dtype=VariableView, got dtype=" +
103 : to_string(dtype<bucket<T>>) +
104 : ". See "
105 : "https://scipp.github.io/user-guide/binned-data/"
106 : "computation.html#Event-centric-arithmetic for equivalent operations "
107 : "for binned (event) data.");
108 15428 : return *std::find_if(parents.begin(), parents.end(), is_bins);
109 : }
110 : virtual Variable call_make_bins(const Variable &parent,
111 : const Variable &indices, const Dim dim,
112 : const DType type, const Dimensions &dims,
113 : const units::Unit &unit,
114 : const bool variances) const = 0;
115 :
116 : protected:
117 95548 : const T &buffer(const Variable &var) const {
118 95548 : return requireT<const BinArrayModel<T>>(var.data()).buffer();
119 : }
120 : // cppcheck-suppress constParameter # Overloading on const-ness.
121 87006 : T buffer(Variable &var) const {
122 87006 : return requireT<BinArrayModel<T>>(var.data()).buffer();
123 : }
124 :
125 : public:
126 15429 : Variable create(const DType elem_dtype, const Dimensions &dims,
127 : const units::Unit &unit, const bool variances,
128 : const typename AbstractVariableMaker::parent_list &parents)
129 : const override {
130 15429 : const Variable &parent = bin_parent(parents);
131 15428 : const auto &[parentIndices, dim, buffer] = parent.constituents<T>();
132 15428 : auto [indices, size] =
133 : bin_array_variable_detail::contiguous_indices(parentIndices, dims);
134 15428 : auto bufferDims = buffer.dims();
135 15428 : bufferDims.resize(dim, size);
136 15428 : return call_make_bins(parent, indices, dim, elem_dtype, bufferDims, unit,
137 30856 : variances);
138 15428 : }
139 :
140 12 : Dim elem_dim(const Variable &var) const override {
141 12 : return std::get<1>(var.constituents<T>());
142 : }
143 136837 : DType elem_dtype(const Variable &var) const override {
144 136837 : return std::get<2>(var.constituents<T>()).dtype();
145 : }
146 126520 : units::Unit elem_unit(const Variable &var) const override {
147 126520 : return std::get<2>(var.constituents<T>()).unit();
148 : }
149 38221 : void expect_can_set_elem_unit(const Variable &var,
150 : const units::Unit &u) const override {
151 38221 : if (elem_unit(var) != u && var.is_slice())
152 6 : throw except::UnitError("Partial view on data of variable cannot be "
153 : "used to change the unit.");
154 38215 : }
155 38196 : void set_elem_unit(Variable &var, const units::Unit &u) const override {
156 38196 : std::get<2>(var.constituents<T>()).setUnit(u);
157 38196 : }
158 5 : bool has_masks(const Variable &var) const override {
159 : static_cast<void>(var);
160 : if constexpr (std::is_same_v<T, Variable>)
161 0 : return false;
162 : else
163 5 : return !std::get<2>(var.constituents<T>()).masks().empty();
164 : }
165 154825 : bool has_variances(const Variable &var) const override {
166 154825 : return std::get<2>(var.constituents<T>()).has_variances();
167 : }
168 : core::ElementArrayViewParams
169 182511 : array_params(const Variable &var) const override {
170 182511 : const auto &[indices, dim, buffer] = var.constituents<T>();
171 182511 : auto params = var.array_params();
172 : return {0, // no offset required in buffer since access via indices
173 : params.dims(),
174 : params.strides(),
175 182511 : {dim, buffer.dims(), Strides{buffer.strides()},
176 547533 : bin_array_variable_detail ::index_pair_data(indices)}};
177 182511 : }
178 : };
179 :
180 1 : template <class T> BinArrayModel<T> copy(const BinArrayModel<T> &model) {
181 1 : return BinArrayModel<T>(model.indices()->clone(), model.bin_dim(),
182 2 : copy(model.buffer()));
183 : }
184 :
185 : template <class T>
186 159193 : BinArrayModel<T>::BinArrayModel(const VariableConceptHandle &indices,
187 : const Dim dim, T buffer)
188 159193 : : BinModelBase<Indices>(indices, dim), m_buffer(std::move(buffer)) {}
189 :
190 0 : template <class T> VariableConceptHandle BinArrayModel<T>::clone() const {
191 0 : return std::make_shared<BinArrayModel<T>>(variable::copy(*this));
192 : }
193 :
194 : template <class T>
195 7 : bool BinArrayModel<T>::operator==(const BinArrayModel &other) const noexcept {
196 : using IndexModel = StructureArrayModel<scipp::index_pair, scipp::index>;
197 14 : if (indices()->dtype() != core::dtype<scipp::index_pair> ||
198 14 : other.indices()->dtype() != core::dtype<scipp::index_pair>)
199 0 : return false;
200 7 : const auto &i1 = requireT<const IndexModel>(*indices());
201 7 : const auto &i2 = requireT<const IndexModel>(*other.indices());
202 12 : return equals_impl(i1.values(), i2.values()) &&
203 12 : this->bin_dim() == other.bin_dim() && m_buffer == other.m_buffer;
204 : }
205 :
206 : template <class T>
207 : VariableConceptHandle
208 4 : BinArrayModel<T>::makeDefaultFromParent(const scipp::index size) const {
209 8 : return std::make_shared<BinArrayModel>(
210 4 : bin_array_variable_detail::zero_indices(size), this->bin_dim(),
211 8 : T{m_buffer.slice({this->bin_dim(), 0, 0})});
212 : }
213 :
214 : template <class T>
215 : VariableConceptHandle
216 0 : BinArrayModel<T>::makeDefaultFromParent(const Variable &shape) const {
217 0 : const auto end = cumsum(shape);
218 0 : const auto begin = end - shape;
219 0 : const auto size = bin_array_variable_detail::size_from_end_index(end);
220 : return std::make_shared<BinArrayModel>(
221 0 : zip(begin, begin).data_handle(), this->bin_dim(),
222 0 : resize_default_init(m_buffer, this->bin_dim(), size));
223 0 : }
224 :
225 0 : template <class T> void BinArrayModel<T>::assign(const VariableConcept &other) {
226 0 : *this = requireT<const BinArrayModel<T>>(other);
227 0 : }
228 :
229 : template <class T>
230 : ElementArrayView<const scipp::index_pair>
231 61943 : BinArrayModel<T>::index_values(const core::ElementArrayViewParams &base) const {
232 61943 : return requireT<const StructureArrayModel<scipp::index_pair, scipp::index>>(
233 61943 : *this->indices())
234 61943 : .values(base);
235 : }
236 :
237 : template <class T>
238 159168 : Variable make_bins_impl(Variable indices, const Dim dim, T &&buffer) {
239 159168 : indices.setDataHandle(std::make_unique<variable::BinArrayModel<T>>(
240 159168 : indices.data_handle(), dim, std::move(buffer)));
241 159168 : return indices;
242 : }
243 :
244 : /// Macro for instantiating classes and functions required for support a new
245 : /// bin dtype in Variable.
246 : #define INSTANTIATE_BIN_ARRAY_VARIABLE(name, ...) \
247 : template <> struct model<core::bin<__VA_ARGS__>> { \
248 : using type = BinArrayModel<__VA_ARGS__>; \
249 : }; \
250 : template SCIPP_EXPORT BinArrayModel<__VA_ARGS__> copy( \
251 : const BinArrayModel<__VA_ARGS__> &); \
252 : template SCIPP_EXPORT Variable make_bins_impl(Variable, const Dim, \
253 : __VA_ARGS__ &&); \
254 : template class SCIPP_EXPORT BinArrayModel<__VA_ARGS__>; \
255 : INSTANTIATE_VARIABLE_BASE(name, core::bin<__VA_ARGS__>) \
256 : template SCIPP_EXPORT std::tuple<Variable, Dim, __VA_ARGS__> \
257 : Variable::constituents<__VA_ARGS__>() const; \
258 : template SCIPP_EXPORT const __VA_ARGS__ &Variable::bin_buffer<__VA_ARGS__>() \
259 : const; \
260 : template SCIPP_EXPORT __VA_ARGS__ &Variable::bin_buffer<__VA_ARGS__>(); \
261 : template SCIPP_EXPORT std::tuple<Variable, Dim, __VA_ARGS__> \
262 : Variable::constituents<__VA_ARGS__>(); \
263 : template SCIPP_EXPORT std::tuple<Variable, Dim, __VA_ARGS__> \
264 : Variable::to_constituents<__VA_ARGS__>();
265 :
266 : } // namespace scipp::variable
|