Stratax 0.4.0rc1
Loading...
Searching...
No Matches
Reductions.hpp
Go to the documentation of this file.
1
4// TODO: Avoid the initial owning Tensor copy when reducing strided views.
5
6#pragma once
7
18
19#include <numeric>
20#include <functional>
21#include <cmath>
22#include <type_traits>
23#include <utility>
24
25namespace reduction {
26
27namespace detail {
28
47template<bool FindMaximum, Array A>
49std::pair<typename A::value_type, std::size_t> ordered_extreme(const A& arr)
50{
51 auto current = arr.begin();
52 const auto end = arr.end();
53 typename A::value_type selected = *current;
54 std::size_t selected_index = 0;
55 std::size_t current_index = 1;
56
57 // Preserve unordered floating values explicitly: standard extrema algorithms,
58 // including MSVC's vectorized path, can assume NaN-free inputs.
59 for (++current; current != end; ++current, ++current_index)
60 {
61 const typename A::value_type value = *current;
62 const bool replace = FindMaximum ? selected < value : value < selected;
63 if (replace)
64 {
65 selected = value;
66 selected_index = current_index;
67 }
68 }
69
70 return {selected, selected_index};
71}
72
82inline bool advance(
83 const stratax::core::Shape& shape,
84 std::vector<std::size_t>& indices)
85{
86 for (std::size_t d = shape.rank(); d-- > 0;)
87 {
88 ++indices[d];
89
90 if (indices[d] < shape[d])
91 {
92 return true;
93 }
94
95 indices[d] = 0;
96 }
97
98 return false;
99}
100
111template<Array A>
112inline int normalize_axis(const A& arr, int axis)
113{
114 int init = axis;
115 if (axis < 0)
116 {
117 init += arr.rank();
118 }
119
120 return init;
121}
122
134template<Array A>
135inline std::vector<std::size_t> result_shape(const A& arr, int axis, bool keepdims)
136{
137 stratax::core::Shape input_shape = arr.shape();
138
139 std::vector<std::size_t> result_dimensions;
140
141 if (keepdims)
142 {
143 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
144 {
145 if (dimension == static_cast<std::size_t>(axis))
146 {
147 result_dimensions.push_back(1);
148 }
149
150 else
151 {
152 result_dimensions.push_back(input_shape[dimension]);
153 }
154 }
155 }
156
157 else
158 {
159 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
160 {
161 if (dimension != static_cast<std::size_t>(axis))
162 {
163 result_dimensions.push_back(input_shape[dimension]);
164 }
165 }
166 }
167
168 return result_dimensions;
169}
170
177template<Array A, typename Func>
179 decltype(std::declval<Func>()(
180 std::declval<const stratax::container::Tensor<typename A::value_type>&>()));
181
182} // namespace detail
183
213template<Array A, typename Func>
215axis_reduce(const A& array, int axis, Func func, bool keepdims = false)
216{
217 using ResultType = detail::axis_reduce_value_t<A, Func>;
218
219 int Axis = detail::normalize_axis(array, axis);
220
221 if (Axis < 0 || Axis >= static_cast<int>(array.rank()))
222 {
223 throw Exceptions::AxisError("Axis is out of range.");
224 }
225
227 stratax::conversion::to_tensor(array);
228
229 const stratax::core::Shape input_shape = arr.shape();
230
231 std::vector<std::size_t> result_dims =
232 detail::result_shape(array, Axis, keepdims);
233
234 // A zero-dimensional tensor cannot store values in the current API.
235 // Represent scalar reductions as a single-element tensor.
236 if (result_dims.empty())
237 {
238 ResultType scalar_result = func(arr);
240 }
241
243 if (result.empty())
244 {
245 return result;
246 }
247
248 std::vector<std::size_t> output_index(result.rank(), 0);
249 std::vector<stratax::core::Slice> slices;
250
251 do {
252 slices.clear();
253 std::size_t output_position = 0;
254 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
255 {
256 if (static_cast<int>(dimension) == Axis)
257 {
258 slices.push_back(stratax::core::Slice{static_cast<std::ptrdiff_t>(0),
259 static_cast<std::ptrdiff_t>(input_shape[dimension])});
260 }
261
262 else
263 {
264 const std::size_t index = keepdims
265 ? output_index[dimension]
266 : output_index[output_position++];
267
268 slices.push_back(stratax::core::Slice{
269 static_cast<std::ptrdiff_t>(index),
270 static_cast<std::ptrdiff_t>(index + 1)
271 });
272 }
273 }
274 auto s = stratax::indexing::slice(arr, slices);
275 ResultType value = func(s);
276 result(output_index) = value;
277 }
278 while (detail::advance(result.shape(), output_index));
279
280 return result;
281}
282
283// Global Reductions
284
293template<Array A>
295auto sum(const A& arr)
296{
297 using result_type =
299
300 return std::accumulate(
301 arr.begin(),
302 arr.end(),
303 result_type{0});
304}
305
314template<Array A>
316auto prod(const A& arr)
317{
318 using result_type =
320
321 return std::accumulate(
322 arr.begin(),
323 arr.end(),
324 result_type{1},
325 std::multiplies<result_type>{});
326}
327
339template<Array A>
341auto max(const A& arr)
342{
343 if (arr.empty())
344 {
345 throw Exceptions::IndexError("Cannot find the maximum of an empty array.");
346 }
347
348 return detail::ordered_extreme<true>(arr).first;
349}
350
362template<Array A>
364auto min(const A& arr)
365{
366 if (arr.empty())
367 {
368 throw Exceptions::IndexError("Cannot find the minimum of an empty array.");
369 }
370
371 return detail::ordered_extreme<false>(arr).first;
372}
373
383template<Array A>
385auto argmax(const A& arr)
386{
387 if (arr.empty())
388 {
389 throw Exceptions::IndexError("Cannot find argmax of an empty array.");
390 }
391
392 return static_cast<stratax::dtype::int64>(detail::ordered_extreme<true>(arr).second);
393}
394
404template<Array A>
406auto argmin(const A& arr)
407{
408 if (arr.empty())
409 {
410 throw Exceptions::IndexError("Cannot find argmin of an empty array.");
411 }
412
413 return static_cast<stratax::dtype::int64>(detail::ordered_extreme<false>(arr).second);
414}
415
430template<Array A>
431requires (
434)
435double mean(const A& arr)
436{
437 if (arr.empty())
438 {
439 throw Exceptions::ZeroDivisionError("Cannot compute the mean of an empty array.");
440 }
441
442 long double total = 0.0L;
443 for (const auto& value : arr)
444 {
445 total += static_cast<long double>(value);
446 }
447
448 return static_cast<double>(total / static_cast<long double>(arr.size()));
449}
450
463template<Array A>
464requires (
467)
468double var(const A& arr)
469{
470 if (arr.empty())
471 {
472 throw Exceptions::ZeroDivisionError("Cannot compute the variance of an empty array.");
473 }
474
475 double count = 0.0;
476 double mean_value = 0.0;
477 double m2 = 0.0;
478
479 for (const auto& value : arr)
480 {
481 count += 1.0;
482 const double delta = static_cast<double>(value) - mean_value;
483 mean_value += delta / count;
484 const double delta2 = static_cast<double>(value) - mean_value;
485 m2 += delta * delta2;
486 }
487
488 return m2 / count;
489}
490
499template<Array A>
500requires (
503)
504double std(const A& arr)
505{
506 auto vars = var(arr);
507 return std::sqrt(vars);
508}
509
510
511// Axis Reductions
512
523template<Array A>
525auto sum(const A& arr, int axis)
526{
527 return axis_reduce(
528 arr,
529 axis,
530 [](const auto& s) {
531 return reduction::sum(s);
532 });
533}
534
546template<Array A>
548auto sum(const A& arr, int axis, bool keepdims)
549{
550 return axis_reduce(
551 arr,
552 axis,
553 [](const auto& s) {
554 return reduction::sum(s);
555 },
556 keepdims);
557}
558
569template<Array A>
571auto prod(const A& arr, int axis)
572{
573 return axis_reduce(
574 arr,
575 axis,
576 [](const auto& s) {
577 return reduction::prod(s);
578 });
579}
580
592template<Array A>
594auto prod(const A& arr, int axis, bool keepdims)
595{
596 return axis_reduce(
597 arr,
598 axis,
599 [](const auto& s) {
600 return reduction::prod(s);
601 },
602 keepdims);
603}
604
614template<Array A>
616auto max(const A& arr, int axis)
617{
618 return axis_reduce(
619 arr, axis, [](const auto& s) { return reduction::max(s); });
620}
621
632template<Array A>
634auto max(const A& arr, int axis, bool keepdims)
635{
636 return axis_reduce(
637 arr,
638 axis,
639 [](const auto& s) { return reduction::max(s); },
640 keepdims);
641}
642
652template<Array A>
654auto min(const A& arr, int axis)
655{
656 return axis_reduce(
657 arr, axis, [](const auto& s) { return reduction::min(s); });
658}
659
670template<Array A>
672auto min(const A& arr, int axis, bool keepdims)
673{
674 return axis_reduce(
675 arr,
676 axis,
677 [](const auto& s) { return reduction::min(s); },
678 keepdims);
679}
680
690template<Array A>
692auto argmax(const A& arr, int axis)
693{
694 return axis_reduce(
695 arr, axis, [](const auto& s) { return reduction::argmax(s); });
696}
697
708template<Array A>
710auto argmax(const A& arr, int axis, bool keepdims)
711{
712 return axis_reduce(
713 arr,
714 axis,
715 [](const auto& s) { return reduction::argmax(s); },
716 keepdims);
717}
718
728template<Array A>
730auto argmin(const A& arr, int axis)
731{
732 return axis_reduce(
733 arr, axis, [](const auto& s) { return reduction::argmin(s); });
734}
735
746template<Array A>
748auto argmin(const A& arr, int axis, bool keepdims)
749{
750 return axis_reduce(
751 arr,
752 axis,
753 [](const auto& s) { return reduction::argmin(s); },
754 keepdims);
755}
756
767template<Array A>
768requires (
771)
773mean(const A& arr, int axis, bool keepdims)
774{
775 return axis_reduce(
776 arr,
777 axis,
778 [](const auto& s) { return reduction::mean(s); },
779 keepdims);
780}
781
791template<Array A>
792requires (
795)
797mean(const A& arr, int axis)
798{
799 return axis_reduce(
800 arr, axis, [](const auto& s) { return reduction::mean(s); });
801}
802
813template<Array A>
814requires (
817)
819var(const A& arr, int axis, bool keepdims)
820{
821 return axis_reduce(
822 arr,
823 axis,
824 [](const auto& s) { return reduction::var(s); },
825 keepdims);
826}
827
837template<Array A>
838requires (
841)
843var(const A& arr, int axis)
844{
845 return axis_reduce(
846 arr, axis, [](const auto& s) { return reduction::var(s); });
847}
848
859template<Array A>
860requires (
863)
865std(const A& arr, int axis, bool keepdims)
866{
867 return axis_reduce(
868 arr,
869 axis,
870 [](const auto& s) { return reduction::std(s); },
871 keepdims);
872}
873
883template<Array A>
884requires (
887)
889std(const A& arr, int axis)
890{
891 return axis_reduce(
892 arr, axis, [](const auto& s) { return reduction::std(s); });
893}
894
895} // namespace reduction
Dtype and array interface concepts.
Copying container conversions and dtype casts.
Stratax exception types derived from std::runtime_error.
Rank-two owning arrays.
Accumulator and result dtype selection for reductions.
typename ReductionTraits< std::remove_cvref_t< T > >::prod_type reduction_prod_t
Accumulator and result type used by reduction::prod.
typename ReductionTraits< std::remove_cvref_t< T > >::sum_type reduction_sum_t
Accumulator and result type used by reduction::sum.
auto min(const A &arr)
Returns the smallest element.
int normalize_axis(const A &arr, int axis)
Converts a possibly negative axis to its non-negative equivalent.
double std(const A &arr)
Returns the population standard deviation of all elements.
auto sum(const A &arr)
Returns the sum of all elements in flat iterator order.
double mean(const A &arr)
Returns the arithmetic mean of all elements as double.
bool advance(const stratax::core::Shape &shape, std::vector< std::size_t > &indices)
Advances a multidimensional index in row-major order.
auto argmin(const A &arr)
Returns the flat index of the smallest element.
double var(const A &arr)
Returns the population variance using Welford's online algorithm.
auto max(const A &arr)
Returns the largest element.
decltype(std::declval< Func >()(std::declval< const stratax::container::Tensor< typename A::value_type > & >())) axis_reduce_value_t
Result type produced by invoking an axis-reduction callback.
std::pair< typename A::value_type, std::size_t > ordered_extreme(const A &arr)
Selects an extreme value and its logical index with an ordered scan.
std::vector< std::size_t > result_shape(const A &arr, int axis, bool keepdims)
Computes the dimensions produced by reducing one normalized axis.
stratax::container::Tensor< detail::axis_reduce_value_t< A, Func > > axis_reduce(const A &array, int axis, Func func, bool keepdims=false)
Applies a scalar reduction callback independently along one axis.
auto argmax(const A &arr)
Returns the flat index of the largest element.
auto prod(const A &arr)
Returns the product of all elements in flat iterator order.
Immutable dimension metadata and checked row-major strides.
Raw signed half-open ranges; applying views currently requires positive steps.
Positive-step non-owning slice views with clamped bounds.
Arbitrary-rank owning arrays; rank zero is empty.
std::int64_t int64
Signed 64-bit integer dtype.
Definition Types.hpp:33
Rank-one owning arrays.
Reduction axis is outside the input rank.
Index or slice cannot be used for the requested access.
An explicitly checked divisor is zero.
Arbitrary-rank owning array of supported dtype values.
Definition Tensor.hpp:54
const Shape & shape() const noexcept
Returns the logical shape metadata.
Definition ArrayBase.hpp:82
Stores the dimensions of a multidimensional array.
Definition Shape.hpp:36
size_type rank() const noexcept
Returns the number of dimensions.
Definition Shape.hpp:124
Describes a signed, strided half-open index range.
Definition Slice.hpp:33
Supported integer, floating-point, or complex dtype, excluding bool.
Definition Concepts.hpp:67
Supported non-complex dtype, including bool; floating-point NaNs need not compare ordered.
Definition Concepts.hpp:154