Stratax 0.3.1
Loading...
Searching...
No Matches
Slicing.hpp
1// TODO: deduplicate Tensor slicing implementations.
2// TODO: make slice normalization arithmetic overflow-safe.
3// TODO: improve Tensor slice error messages.
4// TODO: support omitted slice bounds for NumPy-style slicing.
5// TODO: revisit signed strides when implementing views.
6#pragma once
7
8#include <stratax/containers/Matrix.hpp>
9#include <stratax/containers/Tensor.hpp>
10#include <stratax/containers/Vector.hpp>
11#include <stratax/exceptions/Exceptions.hpp>
12#include <stratax/core/Shape.hpp>
13#include <stratax/core/Slice.hpp>
14#include <stratax/core/ArrayView.hpp>
15
16#include <array>
17#include <algorithm>
18#include <cstddef>
19#include <limits>
20#include <vector>
21#include <type_traits>
22#include <utility>
23#include <concepts>
24
25namespace stratax::indexing {
26
28using size_type = std::size_t;
30using difference_type = std::ptrdiff_t;
31
32namespace detail
33{
34
36{
37 difference_type start;
38 difference_type step;
39 size_type size;
40};
41
42template<typename Source>
43using view_element_t = std::conditional_t<
44 std::is_const_v<std::remove_reference_t<Source>>,
45 const typename std::remove_cvref_t<Source>::value_type,
46 typename std::remove_cvref_t<Source>::value_type>;
47
48inline ResolvedSlice normalize_slice(
49 const stratax::core::Slice& slice,
50 size_type extent)
51{
52 if (extent > static_cast<size_type>(std::numeric_limits<difference_type>::max()))
53 {
54 throw Exceptions::IndexError("Slice extent is too large.");
55 }
56
57 const difference_type n = static_cast<difference_type>(extent);
58 difference_type start = slice.start();
59 difference_type stop = slice.stop();
60 const difference_type step = slice.step();
61
62 if (step > 0)
63 {
64 if (start < 0)
65 {
66 start += n;
67 }
68 if (stop < 0)
69 {
70 stop += n;
71 }
72
73 start = std::clamp(start, difference_type{0}, n);
74 stop = std::clamp(stop, difference_type{0}, n);
75
76 if (start >= stop)
77 {
78 return ResolvedSlice{start, step, 0};
79 }
80
81 const difference_type distance = stop - start;
82 const size_type count = static_cast<size_type>((distance + step - 1) / step);
83 return ResolvedSlice{start, step, count};
84 }
85
86 if (start < 0)
87 {
88 start += n;
89 }
90 if (stop < 0 && stop != -1)
91 {
92 stop += n;
93 }
94
95 start = std::clamp(start, difference_type{-1}, n - 1);
96 stop = std::clamp(stop, difference_type{-1}, n - 1);
97
98 if (start <= stop)
99 {
100 return ResolvedSlice{start, step, 0};
101 }
102
103 const difference_type stride = -step;
104 const difference_type distance = start - stop;
105 const size_type count = static_cast<size_type>((distance + stride - 1) / stride);
106 return ResolvedSlice{start, step, count};
107}
108
109} // namespace detail
110
111namespace detail {
112
113template<typename Vector>
114auto make_vector_slice_view(
115 Vector& vec,
116 const stratax::core::Slice& slice)
117{
118 const auto resolved =
119 detail::normalize_slice(
120 slice,
121 vec.size());
122
123 if (resolved.step < 0)
124 {
126 "Negative-step views are not supported.");
127 }
128
129 const auto offset =
130 static_cast<size_type>(resolved.start);
131
132 const stratax::core::Shape shape{
133 resolved.size
134 };
135
136 const stratax::core::Shape strides{
137 static_cast<size_type>(resolved.step)
138 };
139
141 vec.data() + offset,
142 shape,
143 strides);
144}
145
146template<typename Matrix>
147auto make_matrix_slice_view(
148 Matrix& mat,
149 const stratax::core::Slice& rows,
150 const stratax::core::Slice& cols)
151{
152 const auto resolved_rows =
153 detail::normalize_slice(
154 rows,
155 mat.rows());
156
157 const auto resolved_cols =
158 detail::normalize_slice(
159 cols,
160 mat.cols());
161
162 if (resolved_rows.step < 0 || resolved_cols.step < 0)
163 {
165 "Negative-step views are not supported.");
166 }
167
168 const size_type offset =
169 static_cast<size_type>(resolved_rows.start) * mat.strides()[0]
170 + static_cast<size_type>(resolved_cols.start) * mat.strides()[1];
171
172 const stratax::core::Shape shape{
173 resolved_rows.size,
174 resolved_cols.size
175 };
176
177 const stratax::core::Shape strides{
178 mat.strides()[0] * static_cast<size_type>(resolved_rows.step),
179 mat.strides()[1] * static_cast<size_type>(resolved_cols.step)
180 };
181
183 mat.data() + offset,
184 shape,
185 strides);
186}
187
188} // namespace detail
189
190template<typename T>
191auto slice(
193 const stratax::core::Slice& slice)
194{
195 return detail::make_vector_slice_view(vec, slice);
196}
197
198template<typename T>
199auto slice(
201 const stratax::core::Slice& slice)
202{
203 return detail::make_vector_slice_view(vec, slice);
204}
205
206template<typename T>
207auto slice(
209 const stratax::core::Slice& rows,
210 const stratax::core::Slice& cols)
211{
212 return detail::make_matrix_slice_view(mat, rows, cols);
213}
214
215template<typename T>
216auto slice(
218 const stratax::core::Slice& rows,
219 const stratax::core::Slice& cols)
220{
221 return detail::make_matrix_slice_view(mat, rows, cols);
222}
223
224namespace detail {
225
226template<typename Tensor, typename... Slices>
227requires (
228 std::same_as<
229 std::remove_cvref_t<Slices>,
231 > && ...
232)
233auto make_tensor_slice_view(
234 Tensor& tensor,
235 Slices... slices)
236{
237 std::array<stratax::core::Slice, sizeof...(Slices)> ranges{
238 slices...
239 };
240
241 if (ranges.size() != tensor.rank())
242 {
244 "The number of slices must match the tensor rank.");
245 }
246
247 std::array<
249 sizeof...(Slices)
250 > resolved{};
251
252 std::array<size_type, sizeof...(Slices)> out_dims{};
253
254 for (size_type dim = 0; dim < ranges.size(); ++dim)
255 {
256 resolved[dim] = detail::normalize_slice(
257 ranges[dim],
258 tensor.shape()[dim]);
259
260 out_dims[dim] = resolved[dim].size;
261 }
262
263 const auto view_shape = stratax::core::Shape(
264 std::vector<size_type>(
265 out_dims.begin(),
266 out_dims.end()));
267
268 const auto& tensor_strides = tensor.strides();
269
270 size_type offset = 0;
271
272 std::vector<size_type> view_stride_values;
273 view_stride_values.reserve(resolved.size());
274
275 for (size_type dim = 0; dim < resolved.size(); ++dim)
276 {
277 if (resolved[dim].step < 0)
278 {
280 "Negative-step views are not supported.");
281 }
282
283 const auto start =
284 static_cast<size_type>(resolved[dim].start);
285
286 if (tensor_strides[dim] != 0 &&
287 start > std::numeric_limits<size_type>::max() / tensor_strides[dim])
288 {
289 throw Exceptions::DimensionError("Tensor slice offset overflow.");
290 }
291
292 const auto offset_term = start * tensor_strides[dim];
293
294 if (offset > std::numeric_limits<size_type>::max() - offset_term)
295 {
296 throw Exceptions::DimensionError("Tensor slice offset overflow.");
297 }
298
299 offset += offset_term;
300
301 const auto step = static_cast<size_type>(resolved[dim].step);
302
303 if (step != 0 &&
304 tensor_strides[dim] > std::numeric_limits<size_type>::max() / step)
305 {
306 throw Exceptions::DimensionError("Tensor slice stride overflow.");
307 }
308
309 view_stride_values.push_back(tensor_strides[dim] * step);
310 }
311
312 const stratax::core::Shape view_strides(
313 view_stride_values);
314
316 tensor.data() + offset,
317 view_shape,
318 view_strides);
319}
320
321template<typename Tensor>
322auto make_tensor_slice_view(
323 Tensor& tensor,
324 const std::vector<stratax::core::Slice>& slices)
325{
326 if (slices.size() != tensor.rank())
327 {
329 "The number of slices must match the tensor rank.");
330 }
331
332 std::vector<detail::ResolvedSlice> resolved(
333 slices.size());
334
335 std::vector<size_type> out_dims(
336 slices.size());
337
338 for (size_type dim = 0; dim < slices.size(); ++dim)
339 {
340 resolved[dim] = detail::normalize_slice(
341 slices[dim],
342 tensor.shape()[dim]);
343
344 out_dims[dim] = resolved[dim].size;
345 }
346
347 const auto view_shape =
348 stratax::core::Shape(out_dims);
349
350 const auto& tensor_strides = tensor.strides();
351
352 size_type offset = 0;
353
354 std::vector<size_type> view_stride_values;
355 view_stride_values.reserve(resolved.size());
356
357 for (size_type dim = 0; dim < resolved.size(); ++dim)
358 {
359 if (resolved[dim].step < 0)
360 {
362 "Negative-step views are not supported.");
363 }
364
365 const auto start =
366 static_cast<size_type>(resolved[dim].start);
367
368 if (tensor_strides[dim] != 0 &&
369 start > std::numeric_limits<size_type>::max() / tensor_strides[dim])
370 {
371 throw Exceptions::DimensionError("Tensor slice offset overflow.");
372 }
373
374 const auto offset_term = start * tensor_strides[dim];
375
376 if (offset > std::numeric_limits<size_type>::max() - offset_term)
377 {
378 throw Exceptions::DimensionError("Tensor slice offset overflow.");
379 }
380
381 offset += offset_term;
382
383 const auto step = static_cast<size_type>(resolved[dim].step);
384
385 if (step != 0 &&
386 tensor_strides[dim] > std::numeric_limits<size_type>::max() / step)
387 {
388 throw Exceptions::DimensionError("Tensor slice stride overflow.");
389 }
390
391 view_stride_values.push_back(tensor_strides[dim] * step);
392 }
393
394 const stratax::core::Shape view_strides(
395 view_stride_values);
396
398 tensor.data() + offset,
399 view_shape,
400 view_strides);
401}
402
403} // namespace detail
404
405template<typename T, typename... Slices>
406requires (
407 std::same_as<
408 std::remove_cvref_t<Slices>,
410 > && ...
411)
412auto slice(stratax::container::Tensor<T>& tensor, Slices... slices)
413{
414 return detail::make_tensor_slice_view(tensor, slices...);
415}
416
417template<typename T, typename... Slices>
418requires (
419 std::same_as<
420 std::remove_cvref_t<Slices>,
422 > && ...
423)
424auto slice(const stratax::container::Tensor<T>& tensor, Slices... slices)
425{
426 return detail::make_tensor_slice_view(tensor, slices...);
427}
428
429template<typename T>
430auto slice(
432 const std::vector<stratax::core::Slice>& slices)
433{
434 return detail::make_tensor_slice_view(tensor, slices);
435}
436
437template<typename T>
438auto slice(
439 const stratax::container::Tensor<T>& tensor,
440 const std::vector<stratax::core::Slice>& slices)
441{
442 return detail::make_tensor_slice_view(tensor, slices);
443}
444
445template<typename T>
447slice(
449 const std::vector<stratax::core::Slice>& slices)
450{
451 if (slices.size() != view.rank())
452 {
454 "The number of slices must match the view rank.");
455 }
456
457 std::vector<detail::ResolvedSlice> resolved(slices.size());
458 std::vector<size_type> out_dims(slices.size());
459 std::vector<size_type> out_strides(slices.size());
460
461 size_type start_offset = 0;
462
463 for (size_type dim = 0; dim < slices.size(); ++dim)
464 {
465 resolved[dim] = detail::normalize_slice(
466 slices[dim],
467 view.shape()[dim]);
468
469 out_dims[dim] = resolved[dim].size;
470
471 if (resolved[dim].step < 0)
472 {
474 "Negative-step views are not supported.");
475 }
476
477 const size_type start =
478 static_cast<size_type>(resolved[dim].start);
479
480 const size_type step =
481 static_cast<size_type>(resolved[dim].step);
482
483 if (view.strides()[dim] != 0 &&
484 start >
485 std::numeric_limits<size_type>::max() /
486 view.strides()[dim])
487 {
489 "ArrayView slice offset overflow.");
490 }
491
492 const size_type start_term =
493 start * view.strides()[dim];
494
495 if (start_offset >
496 std::numeric_limits<size_type>::max() -
497 start_term)
498 {
500 "ArrayView slice offset overflow.");
501 }
502
503 start_offset += start_term;
504
505 if (step != 0 &&
506 view.strides()[dim] >
507 std::numeric_limits<size_type>::max() /
508 step)
509 {
511 "ArrayView slice stride overflow.");
512 }
513
514 out_strides[dim] =
515 view.strides()[dim] * step;
516 }
517
518 const stratax::core::Shape out_shape(out_dims);
519 const stratax::core::Shape out_stride_shape(out_strides);
520
521 auto* data = view.data();
522
523 if (out_shape.elements() != 0)
524 {
525 data += start_offset;
526 }
527
529 data,
530 out_shape,
531 out_stride_shape);
532}
533
534} // namespace stratax::indexing
Two-dimensional owning array of numeric values.
Definition Matrix.hpp:46
Arbitrary-rank owning array of numeric values.
Definition Tensor.hpp:50
One-dimensional owning array of numeric values.
Definition Vector.hpp:44
Stores the dimensions of a multidimensional array.
Definition Shape.hpp:33
Shape strides() const
Computes canonical row-major strides for this shape.
Definition Shape.hpp:138
Describes a signed, strided half-open index range.
Definition Slice.hpp:31