Stratax 0.4.0rc1
Loading...
Searching...
No Matches
Slicing.hpp
Go to the documentation of this file.
1
4// TODO: deduplicate Tensor slicing implementations.
5// TODO: improve Tensor slice error messages.
6// TODO: support omitted slice bounds for NumPy-style slicing.
7// TODO: revisit signed strides when implementing views.
8#pragma once
9
17
18#include <array>
19#include <algorithm>
20#include <cstddef>
21#include <limits>
22#include <vector>
23#include <type_traits>
24#include <utility>
25#include <concepts>
26
27namespace stratax::indexing {
28
30using size_type = std::size_t;
32using difference_type = std::ptrdiff_t;
33
34namespace detail
35{
36
47
49template<typename Source>
50using view_element_t = std::conditional_t<
51 std::is_const_v<std::remove_reference_t<Source>>,
52 const typename std::remove_cvref_t<Source>::value_type,
53 typename std::remove_cvref_t<Source>::value_type>;
54
65 const stratax::core::Slice& slice,
66 size_type extent)
67{
68 if (extent > static_cast<size_type>(std::numeric_limits<difference_type>::max()))
69 {
70 throw Exceptions::IndexError("Slice extent is too large.");
71 }
72
73 const difference_type n = static_cast<difference_type>(extent);
74 difference_type start = slice.start();
75 difference_type stop = slice.stop();
76 const difference_type step = slice.step();
77
78 if (step > 0)
79 {
80 if (start < 0)
81 {
82 start += n;
83 }
84 if (stop < 0)
85 {
86 stop += n;
87 }
88
89 start = std::clamp(start, difference_type{0}, n);
90 stop = std::clamp(stop, difference_type{0}, n);
91
92 if (start >= stop)
93 {
94 return ResolvedSlice{start, step, 0};
95 }
96
97 const size_type count = stratax::core::Slice{start, stop, step}.size();
98 return ResolvedSlice{start, step, count};
99 }
100
101 if (start < 0)
102 {
103 start += n;
104 }
105 if (stop < 0 && stop != -1)
106 {
107 stop += n;
108 }
109
110 start = std::clamp(start, difference_type{-1}, n - 1);
111 stop = std::clamp(stop, difference_type{-1}, n - 1);
112
113 if (start <= stop)
114 {
115 return ResolvedSlice{start, step, 0};
116 }
117
118 const size_type count = stratax::core::Slice{start, stop, step}.size();
119 return ResolvedSlice{start, step, count};
120}
121
122} // namespace detail
123
124namespace detail {
125
127template<typename Vector>
129 Vector& vec,
130 const stratax::core::Slice& slice)
131{
132 const auto resolved =
133 detail::normalize_slice(
134 slice,
135 vec.size());
136
137 if (resolved.step < 0)
138 {
140 "Negative-step views are not supported.");
141 }
142
143 const auto offset =
144 static_cast<size_type>(resolved.start);
145
146 const stratax::core::Shape shape{
147 resolved.size
148 };
149
150 const stratax::core::Shape strides{
151 static_cast<size_type>(resolved.step)
152 };
153
155 shape.elements() == 0 ? vec.data() : vec.data() + offset,
156 shape,
157 strides);
158}
159
161template<typename Matrix>
163 Matrix& mat,
164 const stratax::core::Slice& rows,
165 const stratax::core::Slice& cols)
166{
167 const auto resolved_rows =
168 detail::normalize_slice(
169 rows,
170 mat.rows());
171
172 const auto resolved_cols =
173 detail::normalize_slice(
174 cols,
175 mat.cols());
176
177 if (resolved_rows.step < 0 || resolved_cols.step < 0)
178 {
180 "Negative-step views are not supported.");
181 }
182
183 const size_type offset =
184 static_cast<size_type>(resolved_rows.start) * mat.strides()[0]
185 + static_cast<size_type>(resolved_cols.start) * mat.strides()[1];
186
187 const stratax::core::Shape shape{
188 resolved_rows.size,
189 resolved_cols.size
190 };
191
192 if (mat.strides()[0] != 0 && static_cast<size_type>(resolved_rows.step) >
193 std::numeric_limits<size_type>::max() / mat.strides()[0])
194 {
195 throw Exceptions::DimensionError("Matrix slice stride overflow.");
196 }
197
198 const stratax::core::Shape strides{
199 mat.strides()[0] * static_cast<size_type>(resolved_rows.step),
200 mat.strides()[1] * static_cast<size_type>(resolved_cols.step)
201 };
202
204 shape.elements() == 0 ? mat.data() : mat.data() + offset,
205 shape,
206 strides);
207}
208
209} // namespace detail
210
225template<typename T>
226auto slice(
228 const stratax::core::Slice& slice)
229{
230 return detail::make_vector_slice_view(vec, slice);
231}
232
247template<typename T>
248auto slice(
250 const stratax::core::Slice& slice)
251{
252 return detail::make_vector_slice_view(vec, slice);
253}
254
270template<typename T>
271auto slice(
273 const stratax::core::Slice& rows,
274 const stratax::core::Slice& cols)
275{
276 return detail::make_matrix_slice_view(mat, rows, cols);
277}
278
294template<typename T>
295auto slice(
297 const stratax::core::Slice& rows,
298 const stratax::core::Slice& cols)
299{
300 return detail::make_matrix_slice_view(mat, rows, cols);
301}
302
303namespace detail {
304
306template<typename Tensor, typename... Slices>
307requires (
308 std::same_as<
309 std::remove_cvref_t<Slices>,
311 > && ...
312)
314 Tensor& tensor,
315 Slices... slices)
316{
317 std::array<stratax::core::Slice, sizeof...(Slices)> ranges{
318 slices...
319 };
320
321 if (ranges.size() != tensor.rank())
322 {
324 "The number of slices must match the tensor rank.");
325 }
326
327 std::array<
329 sizeof...(Slices)
330 > resolved{};
331
332 std::array<size_type, sizeof...(Slices)> out_dims{};
333
334 for (size_type dim = 0; dim < ranges.size(); ++dim)
335 {
336 resolved[dim] = detail::normalize_slice(
337 ranges[dim],
338 tensor.shape()[dim]);
339
340 out_dims[dim] = resolved[dim].size;
341 }
342
343 const auto view_shape = stratax::core::Shape(
344 std::vector<size_type>(
345 out_dims.begin(),
346 out_dims.end()));
347
348 const auto& tensor_strides = tensor.strides();
349
350 size_type offset = 0;
351
352 std::vector<size_type> view_stride_values;
353 view_stride_values.reserve(resolved.size());
354
355 for (size_type dim = 0; dim < resolved.size(); ++dim)
356 {
357 if (resolved[dim].step < 0)
358 {
360 "Negative-step views are not supported.");
361 }
362
363 const auto start =
364 static_cast<size_type>(resolved[dim].start);
365
366 if (tensor_strides[dim] != 0 &&
367 start > std::numeric_limits<size_type>::max() / tensor_strides[dim])
368 {
369 throw Exceptions::DimensionError("Tensor slice offset overflow.");
370 }
371
372 const auto offset_term = start * tensor_strides[dim];
373
374 if (offset > std::numeric_limits<size_type>::max() - offset_term)
375 {
376 throw Exceptions::DimensionError("Tensor slice offset overflow.");
377 }
378
379 offset += offset_term;
380
381 const auto step = static_cast<size_type>(resolved[dim].step);
382
383 if (step != 0 &&
384 tensor_strides[dim] > std::numeric_limits<size_type>::max() / step)
385 {
386 throw Exceptions::DimensionError("Tensor slice stride overflow.");
387 }
388
389 view_stride_values.push_back(tensor_strides[dim] * step);
390 }
391
392 const stratax::core::Shape view_strides(
393 view_stride_values);
394
396 view_shape.elements() == 0 ? tensor.data() : tensor.data() + offset,
397 view_shape,
398 view_strides);
399}
400
402template<typename Tensor>
404 Tensor& tensor,
405 const std::vector<stratax::core::Slice>& slices)
406{
407 if (slices.size() != tensor.rank())
408 {
410 "The number of slices must match the tensor rank.");
411 }
412
413 std::vector<detail::ResolvedSlice> resolved(
414 slices.size());
415
416 std::vector<size_type> out_dims(
417 slices.size());
418
419 for (size_type dim = 0; dim < slices.size(); ++dim)
420 {
421 resolved[dim] = detail::normalize_slice(
422 slices[dim],
423 tensor.shape()[dim]);
424
425 out_dims[dim] = resolved[dim].size;
426 }
427
428 const auto view_shape =
429 stratax::core::Shape(out_dims);
430
431 const auto& tensor_strides = tensor.strides();
432
433 size_type offset = 0;
434
435 std::vector<size_type> view_stride_values;
436 view_stride_values.reserve(resolved.size());
437
438 for (size_type dim = 0; dim < resolved.size(); ++dim)
439 {
440 if (resolved[dim].step < 0)
441 {
443 "Negative-step views are not supported.");
444 }
445
446 const auto start =
447 static_cast<size_type>(resolved[dim].start);
448
449 if (tensor_strides[dim] != 0 &&
450 start > std::numeric_limits<size_type>::max() / tensor_strides[dim])
451 {
452 throw Exceptions::DimensionError("Tensor slice offset overflow.");
453 }
454
455 const auto offset_term = start * tensor_strides[dim];
456
457 if (offset > std::numeric_limits<size_type>::max() - offset_term)
458 {
459 throw Exceptions::DimensionError("Tensor slice offset overflow.");
460 }
461
462 offset += offset_term;
463
464 const auto step = static_cast<size_type>(resolved[dim].step);
465
466 if (step != 0 &&
467 tensor_strides[dim] > std::numeric_limits<size_type>::max() / step)
468 {
469 throw Exceptions::DimensionError("Tensor slice stride overflow.");
470 }
471
472 view_stride_values.push_back(tensor_strides[dim] * step);
473 }
474
475 const stratax::core::Shape view_strides(
476 view_stride_values);
477
479 view_shape.elements() == 0 ? tensor.data() : tensor.data() + offset,
480 view_shape,
481 view_strides);
482}
483
484} // namespace detail
485
501template<typename T, typename... Slices>
502requires (
503 std::same_as<
504 std::remove_cvref_t<Slices>,
506 > && ...
507)
508auto slice(stratax::container::Tensor<T>& tensor, Slices... slices)
509{
510 return detail::make_tensor_slice_view(tensor, slices...);
511}
512
528template<typename T, typename... Slices>
529requires (
530 std::same_as<
531 std::remove_cvref_t<Slices>,
533 > && ...
534)
535auto slice(const stratax::container::Tensor<T>& tensor, Slices... slices)
536{
537 return detail::make_tensor_slice_view(tensor, slices...);
538}
539
555template<typename T>
556auto slice(
558 const std::vector<stratax::core::Slice>& slices)
559{
560 return detail::make_tensor_slice_view(tensor, slices);
561}
562
578template<typename T>
579auto slice(
580 const stratax::container::Tensor<T>& tensor,
581 const std::vector<stratax::core::Slice>& slices)
582{
583 return detail::make_tensor_slice_view(tensor, slices);
584}
585
601template<typename T>
605 const std::vector<stratax::core::Slice>& slices)
606{
607 if (slices.size() != view.rank())
608 {
610 "The number of slices must match the view rank.");
611 }
612
613 std::vector<detail::ResolvedSlice> resolved(slices.size());
614 std::vector<size_type> out_dims(slices.size());
615 std::vector<size_type> out_strides(slices.size());
616
617 size_type start_offset = 0;
618
619 for (size_type dim = 0; dim < slices.size(); ++dim)
620 {
621 resolved[dim] = detail::normalize_slice(
622 slices[dim],
623 view.shape()[dim]);
624
625 out_dims[dim] = resolved[dim].size;
626
627 if (resolved[dim].step < 0)
628 {
630 "Negative-step views are not supported.");
631 }
632
633 const size_type start =
634 static_cast<size_type>(resolved[dim].start);
635
636 const size_type step =
637 static_cast<size_type>(resolved[dim].step);
638
639 if (view.strides()[dim] != 0 &&
640 start >
641 std::numeric_limits<size_type>::max() /
642 view.strides()[dim])
643 {
645 "ArrayView slice offset overflow.");
646 }
647
648 const size_type start_term =
649 start * view.strides()[dim];
650
651 if (start_offset >
652 std::numeric_limits<size_type>::max() -
653 start_term)
654 {
656 "ArrayView slice offset overflow.");
657 }
658
659 start_offset += start_term;
660
661 if (step != 0 &&
662 view.strides()[dim] >
663 std::numeric_limits<size_type>::max() /
664 step)
665 {
667 "ArrayView slice stride overflow.");
668 }
669
670 out_strides[dim] =
671 view.strides()[dim] * step;
672 }
673
674 const stratax::core::Shape out_shape(out_dims);
675 const stratax::core::Shape out_stride_shape(out_strides);
676
677 auto* data = view.data();
678
679 if (out_shape.elements() != 0)
680 {
681 data += start_offset;
682 }
683
685 data,
686 out_shape,
687 out_stride_shape);
688}
689
690} // namespace stratax::indexing
Non-owning views and logical random-access iterators.
Stratax exception types derived from std::runtime_error.
std::size_t offset(const core::Shape &strides, const IndexContainer &indices)
Computes an unchecked flat offset from strides and normalized indices.
Definition Indexing.hpp:28
Rank-two owning arrays.
std::ptrdiff_t difference_type
Signed type for Python-style indices.
Definition Normalize.hpp:15
std::size_t size_type
Unsigned extent and normalized-index type.
Definition Normalize.hpp:13
Immutable dimension metadata and checked row-major strides.
Raw signed half-open ranges; applying views currently requires positive steps.
auto make_tensor_slice_view(Tensor &tensor, Slices... slices)
Constructs a view with one slice per axis; see the public Tensor slice overloads.
Definition Slicing.hpp:313
auto make_matrix_slice_view(Matrix &mat, const stratax::core::Slice &rows, const stratax::core::Slice &cols)
Constructs a two-axis view; see the public Matrix slice overload.
Definition Slicing.hpp:162
auto slice(stratax::container::Vector< T > &vec, const stratax::core::Slice &slice)
Returns a non-owning Vector slice view (mutable when T is non-const).
Definition Slicing.hpp:226
std::conditional_t< std::is_const_v< std::remove_reference_t< Source > >, const typename std::remove_cvref_t< Source >::value_type, typename std::remove_cvref_t< Source >::value_type > view_element_t
Element type preserving the source array constness.
Definition Slicing.hpp:53
ResolvedSlice normalize_slice(const stratax::core::Slice &slice, size_type extent)
Normalizes explicit bounds against an extent without accessing storage.
Definition Slicing.hpp:64
auto make_vector_slice_view(Vector &vec, const stratax::core::Slice &slice)
Constructs a one-axis view; see the public Vector slice overload.
Definition Slicing.hpp:128
Arbitrary-rank owning arrays; rank zero is empty.
Rank-one owning arrays.
Invalid dimension or overflow in dimension, element-count, or stride arithmetic.
Index or slice cannot be used for the requested access.
Number of axes or supplied multidimensional indices does not match the required rank.
Two-dimensional owning array of supported dtype values.
Definition Matrix.hpp:49
Arbitrary-rank owning array of supported dtype values.
Definition Tensor.hpp:54
One-dimensional owning array of supported dtype values.
Definition Vector.hpp:47
Non-owning view of elements with unsigned element strides.
Definition ArrayView.hpp:38
const Shape & strides() const noexcept
Stored element strides.
const Shape & shape() const noexcept
Logical extents.
pointer data() noexcept
Base storage pointer; empty views may have non-null pointers.
size_type rank() const noexcept
Number of logical axes.
Stores the dimensions of a multidimensional array.
Definition Shape.hpp:36
size_type elements() const
Computes the total number of elements described by the shape.
Definition Shape.hpp:94
Shape strides() const
Computes canonical row-major strides for this shape.
Definition Shape.hpp:142
Describes a signed, strided half-open index range.
Definition Slice.hpp:33
size_type size() const noexcept
Returns the number of indices described by the raw range.
Definition Slice.hpp:87
Normalized first index, signed step, and selected element count.
Definition Slicing.hpp:39
difference_type step
Nonzero signed step.
Definition Slicing.hpp:43
size_type size
Number of selected elements.
Definition Slicing.hpp:45
difference_type start
First normalized index; not dereferenceable for an empty slice.
Definition Slicing.hpp:41