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/containers/Tensor.hpp>
10#include <stratax/containers/Matrix.hpp>
11#include <stratax/containers/Vector.hpp>
12#include <stratax/core/Slice.hpp>
13#include <stratax/indexing/Slicing.hpp>
14#include <stratax/algorithms/Conversion.hpp>
15#include <stratax/core/ReductionTraits.hpp>
16
17#include <numeric>
18#include <algorithm>
19#include <cmath>
20#include <type_traits>
21#include <utility>
22
23namespace reduction {
24
25namespace detail {
26
36inline bool advance(
37 const stratax::core::Shape& shape,
38 std::vector<std::size_t>& indices)
39{
40 for (std::size_t d = shape.rank(); d-- > 0;)
41 {
42 ++indices[d];
43
44 if (indices[d] < shape[d])
45 {
46 return true;
47 }
48
49 indices[d] = 0;
50 }
51
52 return false;
53}
54
65template<Array A>
66inline int normalize_axis(const A& arr, int axis)
67{
68 int init = axis;
69 if (axis < 0)
70 {
71 init += arr.rank();
72 }
73
74 return init;
75}
76
88template<Array A>
89inline std::vector<std::size_t> result_shape(const A& arr, int axis, bool keepdims)
90{
91 stratax::core::Shape input_shape = arr.shape();
92
93 std::vector<std::size_t> result_dimensions;
94
95 if (keepdims)
96 {
97 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
98 {
99 if (dimension == static_cast<std::size_t>(axis))
100 {
101 result_dimensions.push_back(1);
102 }
103
104 else
105 {
106 result_dimensions.push_back(input_shape[dimension]);
107 }
108 }
109 }
110
111 else
112 {
113 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
114 {
115 if (dimension != static_cast<std::size_t>(axis))
116 {
117 result_dimensions.push_back(input_shape[dimension]);
118 }
119 }
120 }
121
122 return result_dimensions;
123}
124
131template<Array A, typename Func>
132using axis_reduce_value_t =
133 decltype(std::declval<Func>()(
134 std::declval<const stratax::container::Tensor<typename A::value_type>&>()));
135
136} // namespace detail
137
163template<Array A, typename Func>
165axis_reduce(const A& array, int axis, Func func, bool keepdims = false)
166{
167 using ResultType = detail::axis_reduce_value_t<A, Func>;
168
169 int Axis = detail::normalize_axis(array, axis);
170
171 if (Axis < 0 || Axis >= static_cast<int>(array.rank()))
172 {
173 throw Exceptions::AxisError("Axis is out of range.");
174 }
175
177 stratax::conversion::to_tensor(array);
178
179 const stratax::core::Shape input_shape = arr.shape();
180
181 std::vector<std::size_t> result_dims =
182 detail::result_shape(array, Axis, keepdims);
183
184 // A zero-dimensional tensor cannot store values in the current API.
185 // Represent scalar reductions as a single-element tensor.
186 if (result_dims.empty())
187 {
188 ResultType scalar_result = func(arr);
190 }
191
193 if (result.empty())
194 {
195 return result;
196 }
197
198 std::vector<std::size_t> output_index(result.rank(), 0);
199 std::vector<stratax::core::Slice> slices;
200
201 do {
202 slices.clear();
203 std::size_t output_position = 0;
204 for (std::size_t dimension = 0; dimension < input_shape.rank(); ++dimension)
205 {
206 if (static_cast<int>(dimension) == Axis)
207 {
208 slices.push_back(stratax::core::Slice{static_cast<std::ptrdiff_t>(0),
209 static_cast<std::ptrdiff_t>(input_shape[dimension])});
210 }
211
212 else
213 {
214 const std::size_t index = keepdims
215 ? output_index[dimension]
216 : output_index[output_position++];
217
218 slices.push_back(stratax::core::Slice{
219 static_cast<std::ptrdiff_t>(index),
220 static_cast<std::ptrdiff_t>(index + 1)
221 });
222 }
223 }
224 auto s = stratax::indexing::slice(arr, slices);
225 ResultType value = func(s);
226 result(output_index) = value;
227 }
228 while (detail::advance(result.shape(), output_index));
229
230 return result;
231}
232
233// Global Reductions
234
243template<Array A>
245auto sum(const A& arr)
246{
247 using result_type =
248 reduction_sum_t<typename A::value_type>;
249
250 return std::accumulate(
251 arr.begin(),
252 arr.end(),
253 result_type{0});
254}
255
264template<Array A>
266auto prod(const A& arr)
267{
268 using result_type =
269 reduction_prod_t<typename A::value_type>;
270
271 return std::accumulate(
272 arr.begin(),
273 arr.end(),
274 result_type{1},
275 std::multiplies<result_type>{});
276}
277
286template<Array A>
288auto max(const A& arr)
289{
290 if (arr.empty())
291 {
292 throw Exceptions::IndexError("Cannot find the maximum of an empty array.");
293 }
294
295 auto result = std::max_element(
296 arr.begin(),
297 arr.end()
298 );
299
300 return *result;
301}
302
311template<Array A>
313auto min(const A& arr)
314{
315 if (arr.empty())
316 {
317 throw Exceptions::IndexError("Cannot find the minimum of an empty array.");
318 }
319
320 auto result = std::min_element(
321 arr.begin(),
322 arr.end()
323 );
324
325 return *result;
326}
327
336template<Array A>
338auto argmax(const A& arr)
339{
340 if (arr.empty())
341 {
342 throw Exceptions::IndexError("Cannot find argmax of an empty array.");
343 }
344
345 auto result = std::max_element(
346 arr.begin(),
347 arr.end()
348 );
349
350 return static_cast<stratax::dtype::int64>(std::distance(arr.begin(), result));
351}
352
361template<Array A>
363auto argmin(const A& arr)
364{
365 if (arr.empty())
366 {
367 throw Exceptions::IndexError("Cannot find argmin of an empty array.");
368 }
369
370 auto result = std::min_element(
371 arr.begin(),
372 arr.end()
373 );
374
375 return static_cast<stratax::dtype::int64>(std::distance(arr.begin(), result));
376}
377
386template<Array A>
387requires (
390)
391double mean(const A& arr)
392{
393 if (arr.empty())
394 {
395 throw Exceptions::ZeroDivisionError("Cannot compute the mean of an empty array.");
396 }
397
398 return static_cast<double>(sum(arr)) / static_cast<double>(arr.size());
399}
400
409template<Array A>
410requires (
413)
414double var(const A& arr)
415{
416 if (arr.empty())
417 {
418 throw Exceptions::ZeroDivisionError("Cannot compute the variance of an empty array.");
419 }
420
421 double count = 0.0;
422 double mean_value = 0.0;
423 double m2 = 0.0;
424
425 for (const auto& value : arr)
426 {
427 count += 1.0;
428 const double delta = static_cast<double>(value) - mean_value;
429 mean_value += delta / count;
430 const double delta2 = static_cast<double>(value) - mean_value;
431 m2 += delta * delta2;
432 }
433
434 return m2 / count;
435}
436
445template<Array A>
446requires (
449)
450double std(const A& arr)
451{
452 auto vars = var(arr);
453 return std::sqrt(vars);
454}
455
456
457// Axis Reductions
458
468template<Array A>
470auto sum(const A& arr, int axis)
471{
472 return axis_reduce(
473 arr,
474 axis,
475 [](const auto& s) {
476 return reduction::sum(s);
477 });
478}
479
490template<Array A>
492auto sum(const A& arr, int axis, bool keepdims)
493{
494 return axis_reduce(
495 arr,
496 axis,
497 [](const auto& s) {
498 return reduction::sum(s);
499 },
500 keepdims);
501}
502
512template<Array A>
514auto prod(const A& arr, int axis)
515{
516 return axis_reduce(
517 arr,
518 axis,
519 [](const auto& s) {
520 return reduction::prod(s);
521 });
522}
523
534template<Array A>
536auto prod(const A& arr, int axis, bool keepdims)
537{
538 return axis_reduce(
539 arr,
540 axis,
541 [](const auto& s) {
542 return reduction::prod(s);
543 },
544 keepdims);
545}
546
555template<Array A>
557auto max(const A& arr, int axis)
558{
559 return axis_reduce(
560 arr, axis, [](const auto& s) { return reduction::max(s); });
561}
562
572template<Array A>
574auto max(const A& arr, int axis, bool keepdims)
575{
576 return axis_reduce(
577 arr,
578 axis,
579 [](const auto& s) { return reduction::max(s); },
580 keepdims);
581}
582
591template<Array A>
593auto min(const A& arr, int axis)
594{
595 return axis_reduce(
596 arr, axis, [](const auto& s) { return reduction::min(s); });
597}
598
608template<Array A>
610auto min(const A& arr, int axis, bool keepdims)
611{
612 return axis_reduce(
613 arr,
614 axis,
615 [](const auto& s) { return reduction::min(s); },
616 keepdims);
617}
618
627template<Array A>
629auto argmax(const A& arr, int axis)
630{
631 return axis_reduce(
632 arr, axis, [](const auto& s) { return reduction::argmax(s); });
633}
634
644template<Array A>
646auto argmax(const A& arr, int axis, bool keepdims)
647{
648 return axis_reduce(
649 arr,
650 axis,
651 [](const auto& s) { return reduction::argmax(s); },
652 keepdims);
653}
654
663template<Array A>
665auto argmin(const A& arr, int axis)
666{
667 return axis_reduce(
668 arr, axis, [](const auto& s) { return reduction::argmin(s); });
669}
670
680template<Array A>
682auto argmin(const A& arr, int axis, bool keepdims)
683{
684 return axis_reduce(
685 arr,
686 axis,
687 [](const auto& s) { return reduction::argmin(s); },
688 keepdims);
689}
690
700template<Array A>
701requires (
704)
706mean(const A& arr, int axis, bool keepdims)
707{
708 return axis_reduce(
709 arr,
710 axis,
711 [](const auto& s) { return reduction::mean(s); },
712 keepdims);
713}
714
723template<Array A>
724requires (
727)
729mean(const A& arr, int axis)
730{
731 return axis_reduce(
732 arr, axis, [](const auto& s) { return reduction::mean(s); });
733}
734
744template<Array A>
745requires (
748)
750var(const A& arr, int axis, bool keepdims)
751{
752 return axis_reduce(
753 arr,
754 axis,
755 [](const auto& s) { return reduction::var(s); },
756 keepdims);
757}
758
767template<Array A>
768requires (
771)
773var(const A& arr, int axis)
774{
775 return axis_reduce(
776 arr, axis, [](const auto& s) { return reduction::var(s); });
777}
778
788template<Array A>
789requires (
792)
794std(const A& arr, int axis, bool keepdims)
795{
796 return axis_reduce(
797 arr,
798 axis,
799 [](const auto& s) { return reduction::std(s); },
800 keepdims);
801}
802
811template<Array A>
812requires (
815)
817std(const A& arr, int axis)
818{
819 return axis_reduce(
820 arr, axis, [](const auto& s) { return reduction::std(s); });
821}
822
823} // namespace reduction
Arbitrary-rank owning array of numeric values.
Definition Tensor.hpp:50
const Shape & shape() const noexcept
Returns the logical shape metadata.
Definition ArrayBase.hpp:79
Stores the dimensions of a multidimensional array.
Definition Shape.hpp:33
size_type rank() const noexcept
Returns the number of dimensions.
Definition Shape.hpp:121
Describes a signed, strided half-open index range.
Definition Slice.hpp:31