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/validation/Validation.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
24namespace stratax::indexing {
25
27using size_type = std::size_t;
29using difference_type = std::ptrdiff_t;
30
31namespace detail
32{
33
39{
41 difference_type start;
43 difference_type step;
45 size_type size;
46};
47
63inline ResolvedSlice normalize_slice(
64 const stratax::core::Slice& slice,
65 size_type extent,
66 const char* message)
67{
68 if (extent > static_cast<size_type>(std::numeric_limits<difference_type>::max()))
69 {
70 throw Exceptions::IndexError(message);
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 difference_type distance = stop - start;
98 const size_type count = static_cast<size_type>((distance + step - 1) / step);
99 return ResolvedSlice{start, step, count};
100 }
101
102 if (start < 0)
103 {
104 start += n;
105 }
106 if (stop < 0 && stop != -1)
107 {
108 stop += n;
109 }
110
111 start = std::clamp(start, difference_type{-1}, n - 1);
112 stop = std::clamp(stop, difference_type{-1}, n - 1);
113
114 if (start <= stop)
115 {
116 return ResolvedSlice{start, step, 0};
117 }
118
119 const difference_type stride = -step;
120 const difference_type distance = start - stop;
121 const size_type count = static_cast<size_type>((distance + stride - 1) / stride);
122 return ResolvedSlice{start, step, count};
123}
124
125} // namespace detail
126
141template<typename T>
142stratax::container::Vector<T>
143slice(
144 const stratax::container::Vector<T>& vec,
145 const stratax::core::Slice& slice)
146{
147 const auto resolved =
148 detail::normalize_slice(
149 slice,
150 vec.size(),
151 "Vector slice out of bounds.");
152
153 stratax::container::Vector<T> result(resolved.size);
154
155 difference_type source = resolved.start;
156
157 for (size_type i = 0; i < result.size(); ++i)
158 {
159 result[i] = vec[static_cast<size_type>(source)];
160 source += resolved.step;
161 }
162
163 return result;
164}
165
181template<typename T>
182stratax::container::Matrix<T>
183slice(
184 const stratax::container::Matrix<T>& mat,
185 const stratax::core::Slice& rows,
186 const stratax::core::Slice& cols
187)
188{
189 const auto resolved_rows = detail::normalize_slice(
190 rows,
191 mat.rows(),
192 "Matrix row slice out of bounds.");
193 const auto resolved_cols = detail::normalize_slice(
194 cols,
195 mat.cols(),
196 "Matrix column slice out of bounds.");
197
198 stratax::container::Matrix<T> result(resolved_rows.size, resolved_cols.size);
199
200 difference_type source_row = resolved_rows.start;
201 for (size_type out_row = 0; out_row < result.rows(); ++out_row)
202 {
203 difference_type source_col = resolved_cols.start;
204 for (size_type out_col = 0; out_col < result.cols(); ++out_col)
205 {
206 result(out_row, out_col) = mat(
207 static_cast<size_type>(source_row),
208 static_cast<size_type>(source_col));
209 source_col += resolved_cols.step;
210 }
211 source_row += resolved_rows.step;
212 }
213
214 return result;
215}
216
235template<typename T, typename... Slices>
236stratax::container::Tensor<T>
237slice(
238 const stratax::container::Tensor<T>& tensor,
239 Slices... slices
240)
241{
242 static_assert(
243 (std::is_same_v<Slices, stratax::core::Slice> && ...),
244 "All arguments must be Slice."
245 );
246
247 std::array<stratax::core::Slice, sizeof...(Slices)> ranges{slices...};
248
249 if (ranges.size() != tensor.rank())
250 {
251 throw Exceptions::IndexError(
252 "Tensor slice rank must match tensor rank.");
253 }
254
255 std::array<stratax::indexing::detail::ResolvedSlice, sizeof...(Slices)> resolved{};
256 std::array<size_type, sizeof...(Slices)> out_dims{};
257 for (size_type dim = 0; dim < ranges.size(); ++dim)
258 {
259 resolved[dim] = detail::normalize_slice(
260 ranges[dim],
261 tensor.shape()[dim],
262 "Tensor slice out of bounds.");
263 out_dims[dim] = resolved[dim].size;
264 }
265
266 const auto result_shape = stratax::core::Shape(
267 std::vector<size_type>(out_dims.begin(), out_dims.end()));
268 stratax::container::Tensor<T> result(result_shape);
269 const stratax::core::Shape result_strides = result_shape.strides();
270 const auto& tensor_strides = tensor.strides();
271
272 if (result.empty())
273 {
274 return result;
275 }
276
277 for (size_type flat = 0; flat < result.size(); ++flat)
278 {
279 size_type remainder = flat;
280 size_type source_offset = 0;
281
282 for (size_type dim = 0; dim < resolved.size(); ++dim)
283 {
284 const size_type index = remainder / result_strides[dim];
285 remainder %= result_strides[dim];
286
287 const difference_type source_index =
288 resolved[dim].start + static_cast<difference_type>(index) * resolved[dim].step;
289 const size_type term =
290 stratax::core::validation::checked_multiply(
291 static_cast<size_type>(source_index),
292 tensor_strides[dim],
293 "Tensor slice offset overflow.");
294 source_offset =
295 stratax::core::validation::checked_add(
296 source_offset,
297 term,
298 "Tensor slice offset overflow.");
299 }
300
301 result[flat] = tensor[source_offset];
302 }
303
304 return result;
305}
306
323template<typename T>
324stratax::container::Tensor<T>
325slice(
326 const stratax::container::Tensor<T>& tensor,
327 const std::vector<stratax::core::Slice>& slices
328)
329{
330 stratax::core::validation::require_rank(
331 slices.size(),
332 tensor.rank(),
333 "Slice rank must match tensor rank.");
334
335 std::vector<detail::ResolvedSlice> resolved(slices.size());
336 std::vector<size_type> out_dims(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 "Tensor slice out of bounds.");
344 out_dims[dim] = resolved[dim].size;
345 }
346
347 const auto result_shape = stratax::core::Shape(out_dims);
348 stratax::container::Tensor<T> result(result_shape);
349
350 if (result.empty())
351 {
352 return result;
353 }
354
355 const stratax::core::Shape result_strides = result_shape.strides();
356 const auto& tensor_strides = tensor.strides();
357
358 for (size_type flat = 0; flat < result.size(); ++flat)
359 {
360 size_type remainder = flat;
361 size_type source_offset = 0;
362
363 for (size_type dim = 0; dim < resolved.size(); ++dim)
364 {
365 const size_type index = remainder / result_strides[dim];
366 remainder %= result_strides[dim];
367
368 const difference_type source_index =
369 resolved[dim].start + static_cast<difference_type>(index) * resolved[dim].step;
370 const size_type term =
371 stratax::core::validation::checked_multiply(
372 static_cast<size_type>(source_index),
373 tensor_strides[dim],
374 "Tensor slice offset overflow.");
375 source_offset =
376 stratax::core::validation::checked_add(
377 source_offset,
378 term,
379 "Tensor slice offset overflow.");
380 }
381
382 result[flat] = tensor[source_offset];
383 }
384
385 return result;
386}
387
388} // namespace stratax::indexing
size_type rows() const noexcept
Returns the number of rows.
Definition Matrix.hpp:198
size_type cols() const noexcept
Returns the number of columns.
Definition Matrix.hpp:200
const Shape & shape() const noexcept
Returns the logical shape metadata.
Definition ArrayBase.hpp:79
size_type size() const noexcept
Returns the number of stored elements.
Definition ArrayBase.hpp:70
size_type rank() const noexcept
Returns the number of logical dimensions.
Definition ArrayBase.hpp:76
const Shape & strides() const noexcept
Returns the row-major stride metadata.
Definition ArrayBase.hpp:82
Describes a signed, strided half-open index range.
Definition Slice.hpp:31
Normalized slice metadata for one concrete dimension.
Definition Slicing.hpp:39
difference_type step
Signed increment between source indices.
Definition Slicing.hpp:43
size_type size
Number of selected indices.
Definition Slicing.hpp:45
difference_type start
First normalized source index.
Definition Slicing.hpp:41