Stratax 0.3.1
Loading...
Searching...
No Matches
Reductions.hpp
1// TODO: Rewrite axis_reduce to iterate directly over source strides
2// instead of materializing a temporary Tensor slice for each output value.
3
4#pragma once
5
6#include <stratax/core/dtypes/Concepts.hpp>
7#include <stratax/exceptions/Exceptions.hpp>
8#include <stratax/core/Shape.hpp>
9#include <stratax/core/validation/Validation.hpp>
10#include <stratax/containers/Tensor.hpp>
11#include <stratax/containers/Matrix.hpp>
12#include <stratax/containers/Vector.hpp>
13#include <stratax/core/Slice.hpp>
14#include <stratax/indexing/Slicing.hpp>
15#include <stratax/algorithms/Conversion.hpp>
16#include <stratax/core/ReductionTraits.hpp>
17
18#include <numeric>
19#include <algorithm>
20#include <cmath>
21#include <type_traits>
22#include <utility>
23
24namespace reduction {
25
26namespace detail {
27
37inline bool advance(
38 const stratax::core::Shape& shape,
39 std::vector<std::size_t>& indices)
40{
41 for (std::size_t d = shape.rank(); d-- > 0;)
42 {
43 ++indices[d];
44
45 if (indices[d] < shape[d])
46 {
47 return true;
48 }
49
50 indices[d] = 0;
51 }
52
53 return false;
54}
55
66template<Array A>
67inline int normalize_axis(const A& arr, int axis)
68{
69 int init = axis;
70 if (axis < 0)
71 {
72 init += arr.rank();
73 }
74
75 return init;
76}
77
89template<Array A>
90inline std::vector<std::size_t> result_shape(const A& arr, int axis, bool keepdims)
91{
92 stratax::core::Shape input_shape = arr.shape();
93
94 std::vector<std::size_t> result_dimensions;
95
96 if (keepdims)
97 {
98 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
99 {
100 if (dimension == static_cast<std::size_t>(axis))
101 {
102 result_dimensions.push_back(1);
103 }
104
105 else
106 {
107 result_dimensions.push_back(input_shape[dimension]);
108 }
109 }
110 }
111
112 else
113 {
114 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
115 {
116 if (dimension != static_cast<std::size_t>(axis))
117 {
118 result_dimensions.push_back(input_shape[dimension]);
119 }
120 }
121 }
122
123 return result_dimensions;
124}
125
132template<Array A, typename Func>
133using axis_reduce_value_t =
134 decltype(std::declval<Func>()(
135 std::declval<const stratax::container::Tensor<typename A::value_type>&>()));
136
137} // namespace detail
138
164template<Array A, typename Func>
165stratax::container::Tensor<detail::axis_reduce_value_t<A, Func>>
166axis_reduce(const A& array, int axis, Func func, bool keepdims = false)
167{
168 using ResultType = detail::axis_reduce_value_t<A, Func>;
169
170 int Axis = detail::normalize_axis(array, axis);
171
172 if (Axis < 0 || Axis >= static_cast<int>(array.rank()))
173 {
174 throw Exceptions::AxisError("axis is out of range.");
175 }
176
177 stratax::container::Tensor<typename A::value_type> arr =
178 stratax::conversion::to_tensor(array);
179
180 const stratax::core::Shape input_shape = arr.shape();
181
182 std::vector<std::size_t> result_dims =
183 detail::result_shape(array, Axis, keepdims);
184
185 // A zero-dimensional tensor cannot store values in the current API.
186 // Represent scalar reductions as a single-element tensor.
187 if (result_dims.empty())
188 {
189 ResultType scalar_result = func(arr);
190 return stratax::container::Tensor<ResultType>(stratax::core::Shape{1}, scalar_result);
191 }
192
193 stratax::container::Tensor<ResultType> result(stratax::core::Shape{result_dims});
194 if (result.empty())
195 {
196 return result;
197 }
198
199 std::vector<std::size_t> output_index(result.rank(), 0);
200 std::vector<stratax::core::Slice> slices;
201
202 do {
203 slices.clear();
204 std::size_t output_position = 0;
205 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
206 {
207 if (static_cast<int>(dimension) == Axis)
208 {
209 slices.push_back(stratax::core::Slice{static_cast<std::ptrdiff_t>(0),
210 static_cast<std::ptrdiff_t>(input_shape[dimension])});
211 }
212
213 else
214 {
215 const std::size_t index = keepdims
216 ? output_index[dimension]
217 : output_index[output_position++];
218
219 slices.push_back(stratax::core::Slice{
220 static_cast<std::ptrdiff_t>(index),
221 static_cast<std::ptrdiff_t>(index + 1)
222 });
223 }
224 }
225 auto s = stratax::indexing::slice(arr, slices);
226 ResultType value = func(s);
227 result(output_index) = value;
228 }
229 while (detail::advance(result.shape(), output_index));
230
231 return result;
232}
233
234// Global Reductions
235
244template<Array A>
245requires Numeric<typename A::value_type>
246auto sum(const A& arr)
247{
248 using result_type =
249 reduction_sum_t<typename A::value_type>;
250
251 return std::accumulate(
252 arr.begin(),
253 arr.end(),
254 result_type{0});
255}
256
265template<Array A>
266requires Numeric<typename A::value_type>
267auto prod(const A& arr)
268{
269 using result_type =
270 reduction_prod_t<typename A::value_type>;
271
272 return std::accumulate(
273 arr.begin(),
274 arr.end(),
275 result_type{1},
276 std::multiplies<result_type>{});
277}
278
287template<Array A>
288requires Ordered<typename A::value_type>
289auto max(const A& arr)
290{
291 if (arr.empty())
292 {
293 throw Exceptions::IndexError(
294 "Maximum is undefined for an empty array.");
295 }
296
297 auto result = std::max_element(
298 arr.begin(),
299 arr.end()
300 );
301
302 return *result;
303}
304
313template<Array A>
314requires Ordered<typename A::value_type>
315auto min(const A& arr)
316{
317 if (arr.empty())
318 {
319 throw Exceptions::IndexError(
320 "Minimum is undefined for an empty array.");
321 }
322
323 auto result = std::min_element(
324 arr.begin(),
325 arr.end()
326 );
327
328 return *result;
329}
330
339template<Array A>
340requires Ordered<typename A::value_type>
341auto argmax(const A& arr)
342{
343 if (arr.empty())
344 {
345 throw Exceptions::IndexError(
346 "Argmax is undefined for an empty array.");
347 }
348
349 auto result = std::max_element(
350 arr.begin(),
351 arr.end()
352 );
353
354 return static_cast<std::size_t>(std::distance(arr.begin(), result));
355}
356
365template<Array A>
366requires Ordered<typename A::value_type>
367auto argmin(const A& arr)
368{
369 if (arr.empty())
370 {
371 throw Exceptions::IndexError(
372 "Argmin is undefined for an empty array.");
373 }
374
375 auto result = std::min_element(
376 arr.begin(),
377 arr.end()
378 );
379
380 return static_cast<std::size_t>(std::distance(arr.begin(), result));
381}
382
391template<Array A>
392requires (
393 Numeric<typename A::value_type> &&
394 Ordered<typename A::value_type>
395)
396double mean(const A& arr)
397{
398 if (arr.empty())
399 {
400 throw Exceptions::ZeroDivisionError(
401 "Mean is undefined for an empty array.");
402 }
403
404 return static_cast<double>(sum(arr)) / static_cast<double>(arr.size());
405}
406
415template<Array A>
416requires (
417 Numeric<typename A::value_type> &&
418 Ordered<typename A::value_type>
419)
420double var(const A& arr)
421{
422 if (arr.empty())
423 {
424 throw Exceptions::ZeroDivisionError(
425 "Variance is undefined for an empty array.");
426 }
427
428 double count = 0.0;
429 double mean_value = 0.0;
430 double m2 = 0.0;
431
432 for (const auto& value : arr)
433 {
434 count += 1.0;
435 const double delta = static_cast<double>(value) - mean_value;
436 mean_value += delta / count;
437 const double delta2 = static_cast<double>(value) - mean_value;
438 m2 += delta * delta2;
439 }
440
441 return m2 / count;
442}
443
452template<Array A>
453requires (
454 Numeric<typename A::value_type> &&
455 Ordered<typename A::value_type>
456)
457double std(const A& arr)
458{
459 auto vars = var(arr);
460 return std::sqrt(vars);
461}
462
463
464// Axis Reductions
465
475template<Array A>
476requires Numeric<typename A::value_type>
477auto sum(const A& arr, int axis)
478{
479 return axis_reduce(
480 arr,
481 axis,
482 [](const auto& s) {
483 return reduction::sum(s);
484 });
485}
486
497template<Array A>
498requires Numeric<typename A::value_type>
499auto sum(const A& arr, int axis, bool keepdims)
500{
501 return axis_reduce(
502 arr,
503 axis,
504 [](const auto& s) {
505 return reduction::sum(s);
506 },
507 keepdims);
508}
509
519template<Array A>
520requires Numeric<typename A::value_type>
521auto prod(const A& arr, int axis)
522{
523 return axis_reduce(
524 arr,
525 axis,
526 [](const auto& s) {
527 return reduction::prod(s);
528 });
529}
530
541template<Array A>
542requires Numeric<typename A::value_type>
543auto prod(const A& arr, int axis, bool keepdims)
544{
545 return axis_reduce(
546 arr,
547 axis,
548 [](const auto& s) {
549 return reduction::prod(s);
550 },
551 keepdims);
552}
553
562template<Array A>
563requires Ordered<typename A::value_type>
564auto max(const A& arr, int axis)
565{
566 return axis_reduce(
567 arr, axis, [](const auto& s) { return reduction::max(s); });
568}
569
579template<Array A>
580requires Ordered<typename A::value_type>
581auto max(const A& arr, int axis, bool keepdims)
582{
583 return axis_reduce(
584 arr,
585 axis,
586 [](const auto& s) { return reduction::max(s); },
587 keepdims);
588}
589
598template<Array A>
599requires Ordered<typename A::value_type>
600auto min(const A& arr, int axis)
601{
602 return axis_reduce(
603 arr, axis, [](const auto& s) { return reduction::min(s); });
604}
605
615template<Array A>
616requires Ordered<typename A::value_type>
617auto min(const A& arr, int axis, bool keepdims)
618{
619 return axis_reduce(
620 arr,
621 axis,
622 [](const auto& s) { return reduction::min(s); },
623 keepdims);
624}
625
634template<Array A>
635requires Ordered<typename A::value_type>
636auto argmax(const A& arr, int axis)
637{
638 return axis_reduce(
639 arr, axis, [](const auto& s) { return reduction::argmax(s); });
640}
641
651template<Array A>
652requires Ordered<typename A::value_type>
653auto argmax(const A& arr, int axis, bool keepdims)
654{
655 return axis_reduce(
656 arr,
657 axis,
658 [](const auto& s) { return reduction::argmax(s); },
659 keepdims);
660}
661
670template<Array A>
671requires Ordered<typename A::value_type>
672auto argmin(const A& arr, int axis)
673{
674 return axis_reduce(
675 arr, axis, [](const auto& s) { return reduction::argmin(s); });
676}
677
687template<Array A>
688requires Ordered<typename A::value_type>
689auto argmin(const A& arr, int axis, bool keepdims)
690{
691 return axis_reduce(
692 arr,
693 axis,
694 [](const auto& s) { return reduction::argmin(s); },
695 keepdims);
696}
697
707template<Array A>
708requires (
709 Numeric<typename A::value_type> &&
710 Ordered<typename A::value_type>
711)
712stratax::container::Tensor<double>
713mean(const A& arr, int axis, bool keepdims)
714{
715 return axis_reduce(
716 arr,
717 axis,
718 [](const auto& s) { return reduction::mean(s); },
719 keepdims);
720}
721
730template<Array A>
731requires (
732 Numeric<typename A::value_type> &&
733 Ordered<typename A::value_type>
734)
735stratax::container::Tensor<double>
736mean(const A& arr, int axis)
737{
738 return axis_reduce(
739 arr, axis, [](const auto& s) { return reduction::mean(s); });
740}
741
751template<Array A>
752requires (
753 Numeric<typename A::value_type> &&
754 Ordered<typename A::value_type>
755)
756stratax::container::Tensor<double>
757var(const A& arr, int axis, bool keepdims)
758{
759 return axis_reduce(
760 arr,
761 axis,
762 [](const auto& s) { return reduction::var(s); },
763 keepdims);
764}
765
774template<Array A>
775requires (
776 Numeric<typename A::value_type> &&
777 Ordered<typename A::value_type>
778)
779stratax::container::Tensor<double>
780var(const A& arr, int axis)
781{
782 return axis_reduce(
783 arr, axis, [](const auto& s) { return reduction::var(s); });
784}
785
795template<Array A>
796requires (
797 Numeric<typename A::value_type> &&
798 Ordered<typename A::value_type>
799)
800stratax::container::Tensor<double>
801std(const A& arr, int axis, bool keepdims)
802{
803 return axis_reduce(
804 arr,
805 axis,
806 [](const auto& s) { return reduction::std(s); },
807 keepdims);
808}
809
818template<Array A>
819requires (
820 Numeric<typename A::value_type> &&
821 Ordered<typename A::value_type>
822)
823stratax::container::Tensor<double>
824std(const A& arr, int axis)
825{
826 return axis_reduce(
827 arr, axis, [](const auto& s) { return reduction::std(s); });
828}
829
830} // namespace reduction
const Shape & shape() const noexcept
Returns the logical shape metadata.
Definition ArrayBase.hpp:79
size_type rank() const noexcept
Returns the number of dimensions.
Definition Shape.hpp:122