Loading...
Searching...
No Matches
iterator.hpp
1#pragma once
2
3#include <array>
4#include <cassert>
5#include <concepts>
6#include <cstddef>
7#include <type_traits>
8
9namespace tf {
10
27template <std::integral T>
28constexpr bool is_index_range_invalid(T beg, T end, T step) {
29 return ((step == T{0} && beg != end) ||
30 (beg < end && step <= T{0}) || // positive range
31 (beg > end && step >= T{0})); // negative range
32}
33
70template <std::integral T>
71constexpr size_t distance(T beg, T end, T step) {
72 if constexpr (std::is_unsigned_v<T>) {
73 // step is always positive for unsigned types — standard ceiling division
74 return static_cast<size_t>((end - beg + step - T{1}) / step);
75 } else {
76 // signed: step may be positive or negative
77 return static_cast<size_t>((end - beg + step + (step > T{0} ? T{-1} : T{1})) / step);
78 }
79}
80
81
82// ----------------------------------------------------------------------------
83// IndexRange<T, N> (primary template, N > 1)
84// IndexRange<T, 1> (partial specialization, mirrors original IndexRange<T>)
85//
86// Forward-declare the primary template so the specialization can reference it.
87// ----------------------------------------------------------------------------
88
92template <std::integral T, size_t N = 1>
93class IndexRange;
94
95
96// ============================================================================
97// Primary template: N-dimensional index range (N > 1)
98//
99// Represents the Cartesian product of N 1D IndexRange<T, 1> objects.
100//
101// Iteration order is row-major (last dimension is innermost / fastest),
102// matching the natural loop nesting:
103//
104// for i in dim[0]: // outermost
105// for j in dim[1]:
106// ...
107// for k in dim[N-1]: // innermost
108//
109// Flat index 0 corresponds to (beg[0], beg[1], ..., beg[N-1]).
110// ============================================================================
111
164template <std::integral T, size_t N>
166
167public:
168
172 using index_type = T;
173
177 static constexpr size_t rank = N;
178
179 // --------------------------------------------------------------------------
180 // Construction
181 // --------------------------------------------------------------------------
182
189 IndexRange() = default;
190
227 template <typename... Ranges>
228 requires (sizeof...(Ranges) == N) &&
229 (std::same_as<std::remove_cvref_t<Ranges>, IndexRange<T, 1>> && ...)
230 explicit IndexRange(Ranges&&... ranges)
231 : _dims{ std::forward<Ranges>(ranges)... } {}
232
251 explicit IndexRange(const std::array<IndexRange<T, 1>, N>& dims);
252
253 // --------------------------------------------------------------------------
254 // Dimension access
255 // --------------------------------------------------------------------------
256
298 const IndexRange<T, 1>& dim(size_t d) const { return _dims[d]; }
299
325 IndexRange<T, 1>& dim(size_t d) { return _dims[d]; }
326
355 const std::array<IndexRange<T, 1>, N>& dims() const { return _dims; }
356
357 // --------------------------------------------------------------------------
358 // Size queries
359 // --------------------------------------------------------------------------
360
396 size_t size(size_t d) const { return _dims[d].size(); }
397
453 size_t size() const;
454
507 std::pair<IndexRange<T, N>, size_t> slice_ceil(size_t flat_beg, size_t chunk_size) const;
508
561 std::pair<IndexRange<T, N>, size_t> slice_floor(size_t flat_beg, size_t chunk_size) const;
562
603 size_t ceil(size_t chunk_size) const;
604
644 size_t floor(size_t chunk_size) const;
645
646 private:
647
648 std::array<IndexRange<T, 1>, N> _dims;
649};
650
651// ============================================================================
652// Out-of-class definitions — IndexRange<T, N> (primary template)
653// ============================================================================
654
655template <std::integral T, size_t N>
656IndexRange<T, N>::IndexRange(const std::array<IndexRange<T, 1>, N>& dims) : _dims{dims} {}
657
658template <std::integral T, size_t N>
660 size_t total = 1;
661 for (size_t d = 0; d < N; ++d) {
662 size_t s = _dims[d].size();
663 if (s == 0) return d == 0 ? 0 : total; // outermost zero -> 0, inner zero -> outer product
664 total *= s;
665 }
666 return total;
667}
668
669template <std::integral T, size_t N>
670size_t IndexRange<T, N>::ceil(size_t chunk_size) const {
671 // Pass 1: cache all dim sizes (one .size() call each) and find d_active.
672 size_t dim_sizes[N];
673 size_t d_active = N;
674 for (size_t d = 0; d < N; ++d) {
675 dim_sizes[d] = _dims[d].size();
676 if (dim_sizes[d] == 0) { d_active = d; break; }
677 }
678 if (d_active == 0) return 0;
679
680 // Pass 2: walk suffix products backward within [0, d_active) only.
681 // Return the first suffix product >= chunk_size, capped at size().
682 size_t inner_volume = 1;
683 if (inner_volume >= chunk_size) return inner_volume;
684 for (size_t d = d_active; d-- > 0; ) {
685 inner_volume *= dim_sizes[d];
686 if (inner_volume >= chunk_size) return inner_volume;
687 }
688 return inner_volume; // == size() of active dims, capped
689}
690
691template <std::integral T, size_t N>
692size_t IndexRange<T, N>::floor(size_t chunk_size) const {
693 // Pass 1: cache all dim sizes (one .size() call each) and find d_active.
694 size_t dim_sizes[N];
695 size_t d_active = N;
696 for (size_t d = 0; d < N; ++d) {
697 dim_sizes[d] = _dims[d].size();
698 if (dim_sizes[d] == 0) { d_active = d; break; }
699 }
700 if (d_active == 0) return 0;
701
702 // Pass 2: walk suffix products backward within [0, d_active) only.
703 // Return the largest suffix product <= chunk_size.
704 size_t inner_volume = 1;
705 size_t last_fit = 1;
706 for (size_t d = d_active; d-- > 0; ) {
707 size_t next = inner_volume * dim_sizes[d];
708 if (next > chunk_size) break;
709 inner_volume = next;
710 last_fit = inner_volume;
711 }
712 return last_fit;
713}
714
715template <std::integral T, size_t N>
716std::pair<IndexRange<T, N>, size_t>
717IndexRange<T, N>::slice_ceil(size_t flat_beg, size_t chunk_size) const {
718
719 if (chunk_size == 0) {
720 return { *this, 0 };
721 }
722
723 // Find d_active: the index of the first zero-size dimension (or N if none).
724 // Only dimensions [0, d_active) participate in the flat iteration space.
725 // Dimensions [d_active, N) are copied as full extent into the returned box.
726 size_t dim_sizes[N];
727 size_t d_active = N;
728 for (size_t d = 0; d < N; ++d) {
729 dim_sizes[d] = _dims[d].size();
730 if (dim_sizes[d] == 0) { d_active = d; break; }
731 }
732
733 if (d_active == 0) return { *this, 0 }; // outermost dim is zero — no work
734
735 // Decode flat_beg into coords for active dimensions [0, d_active) only.
736 size_t coords[N] = {};
737 size_t temp = flat_beg;
738 for (size_t d = d_active; d-- > 0; ) {
739 coords[d] = temp % dim_sizes[d];
740 temp /= dim_sizes[d];
741 }
742
743 // Find grow_dim within [0, d_active): record each dim BEFORE the budget check
744 // (ceil: round up to the next boundary). Stop when budget met or trailing-zeros fires.
745 size_t grow_dim = d_active - 1;
746 size_t inner_volume = 1;
747 size_t active_inner_vol = 1;
748
749 for (size_t d = d_active; d-- > 0; ) {
750 if (d + 1 < d_active && coords[d + 1] != 0) break; // trailing-zeros rule
751
752 grow_dim = d;
753 active_inner_vol = inner_volume;
754
755 if (inner_volume >= chunk_size) break;
756
757 inner_volume *= dim_sizes[d];
758 }
759
760 // Steps along grow_dim: ceil division, at least 1.
761 size_t steps_left = dim_sizes[grow_dim] - coords[grow_dim];
762 size_t steps_needed = (std::max)(size_t{1}, chunk_size / active_inner_vol);
763 size_t steps_to_take = (std::min)(steps_left, steps_needed);
764
765 // Construct the sub-box. Active dims [0, d_active) are locked/grow/inner
766 // as usual; dims [d_active, N) are copied as full extent (zero-size and beyond).
767 std::array<IndexRange<T, 1>, N> box_dims;
768 for (size_t d = 0; d < N; ++d) {
769 const auto& dim = _dims[d];
770 const T step = dim.step_size();
771 const T beg = dim.begin();
772 if (d >= d_active) {
773 box_dims[d] = dim; // full extent
774 } else if (d < grow_dim) {
775 T b = beg + static_cast<T>(coords[d]) * step;
776 box_dims[d] = IndexRange<T, 1>(b, b + step, step); // locked
777 } else if (d == grow_dim) {
778 box_dims[d] = IndexRange<T, 1>(
779 beg + static_cast<T>(coords[d]) * step,
780 beg + static_cast<T>(coords[d] + steps_to_take) * step,
781 step
782 );
783 } else {
784 box_dims[d] = dim; // full inner extent
785 }
786 }
787
788 return { IndexRange<T, N>(box_dims), steps_to_take * active_inner_vol };
789}
790
791template <std::integral T, size_t N>
792std::pair<IndexRange<T, N>, size_t>
793IndexRange<T, N>::slice_floor(size_t flat_beg, size_t chunk_size) const {
794
795 if (chunk_size == 0) {
796 return { *this, 0 };
797 }
798
799 // Find d_active: the index of the first zero-size dimension (or N if none).
800 // Only dimensions [0, d_active) participate in the flat iteration space.
801 // Dimensions [d_active, N) are copied as full extent into the returned box.
802 size_t dim_sizes[N];
803 size_t d_active = N;
804 for (size_t d = 0; d < N; ++d) {
805 dim_sizes[d] = _dims[d].size();
806 if (dim_sizes[d] == 0) { d_active = d; break; }
807 }
808
809 if (d_active == 0) return { *this, 0 }; // outermost dim is zero — no work
810
811 // Decode flat_beg into coords for active dimensions [0, d_active) only.
812 size_t coords[N] = {};
813 size_t temp = flat_beg;
814 for (size_t d = d_active; d-- > 0; ) {
815 coords[d] = temp % dim_sizes[d];
816 temp /= dim_sizes[d];
817 }
818
819 // Find grow_dim within [0, d_active): commit each dim AFTER the budget check
820 // (floor: round down to the previous boundary).
821 size_t grow_dim = d_active - 1;
822 size_t active_inner_vol = 1;
823 size_t inner_volume = 1;
824
825 for (size_t d = d_active; d-- > 0; ) {
826 if (d + 1 < d_active && coords[d + 1] != 0) break; // trailing-zeros rule
827
828 size_t next_vol = inner_volume * dim_sizes[d];
829 if (next_vol > chunk_size && inner_volume > 1) break;
830
831 grow_dim = d;
832 active_inner_vol = inner_volume;
833 inner_volume = next_vol;
834 }
835
836 // Steps along grow_dim: floor division, at least 1 for forward progress.
837 size_t steps_left = dim_sizes[grow_dim] - coords[grow_dim];
838 size_t steps_needed = (std::max)(size_t{1}, chunk_size / active_inner_vol);
839 size_t steps_to_take = (std::min)(steps_left, steps_needed);
840
841 // Construct the sub-box. Active dims [0, d_active) are locked/grow/inner
842 // as usual; dims [d_active, N) are copied as full extent (zero-size and beyond).
843 std::array<IndexRange<T, 1>, N> box_dims;
844 for (size_t d = 0; d < N; ++d) {
845 const auto& dim = _dims[d];
846 const T step = dim.step_size();
847 const T beg = dim.begin();
848 if (d >= d_active) {
849 box_dims[d] = dim; // full extent
850 } else if (d < grow_dim) {
851 T b = beg + static_cast<T>(coords[d]) * step;
852 box_dims[d] = IndexRange<T, 1>(b, b + step, step); // locked
853 } else if (d == grow_dim) {
854 box_dims[d] = IndexRange<T, 1>(
855 beg + static_cast<T>(coords[d]) * step,
856 beg + static_cast<T>(coords[d] + steps_to_take) * step,
857 step
858 );
859 } else {
860 box_dims[d] = dim; // full inner extent
861 }
862 }
863
864 return { IndexRange<T, N>(box_dims), steps_to_take * active_inner_vol };
865}
866
867
868// ============================================================================
869// Partial specialization: IndexRange<T, 1> (1D case)
870//
871// This is the original IndexRange<T> class, re-expressed as the N=1
872// specialization so that IndexRange<int, 1> and IndexRange<int> (via the
873// default argument N=1 on the primary template forward declaration above) both
874// refer to the same type.
875// ============================================================================
876
912template <std::integral T>
913class IndexRange<T, 1> {
914
915 public:
916
920 using index_type = T;
921
925 static constexpr size_t rank = 1;
926
930 IndexRange() = default;
931
938 explicit IndexRange(T beg, T end, T step_size)
939 : _beg{beg}, _end{end}, _step_size{step_size} {}
940
944 T begin() const { return _beg; }
945
949 T end() const { return _end; }
950
954 T step_size() const { return _step_size; }
955
960 _beg = begin;
961 _end = end;
962 _step_size = step_size;
963 return *this;
964 }
965
969 IndexRange<T, 1>& begin(T new_begin) { _beg = new_begin; return *this; }
970
974 IndexRange<T, 1>& end(T new_end) { _end = new_end; return *this; }
975
979 IndexRange<T, 1>& step_size(T new_step_size) { _step_size = new_step_size; return *this; }
980
993 size_t size() const;
994
1027 IndexRange<T, 1> unravel(size_t part_beg, size_t part_end) const;
1028
1029 private:
1030
1031 T _beg;
1032 T _end;
1033 T _step_size;
1034
1035};
1036
1037template <std::integral T>
1039 return distance(_beg, _end, _step_size);
1040}
1041
1042template <std::integral T>
1043IndexRange<T, 1> IndexRange<T, 1>::unravel(size_t part_beg, size_t part_end) const {
1044 return IndexRange<T, 1>(
1045 static_cast<T>(part_beg) * _step_size + _beg,
1046 static_cast<T>(part_end) * _step_size + _beg,
1047 _step_size
1048 );
1049}
1050
1051// ----------------------------------------------------------------------------
1052// Deduction guide: IndexRange(beg, end, step) -> IndexRange<T, 1>
1053//
1054// Required because IndexRange is now a two-parameter template (T, N).
1055// Without this guide, CTAD cannot deduce N from a three-argument constructor
1056// call such as `tf::IndexRange range(0, 10, 2)`.
1057// ----------------------------------------------------------------------------
1058
1078template <std::integral T>
1080
1081// ==========================================
1082// traits
1083// ==========================================
1084
1089template <typename>
1090constexpr bool is_index_range_v = false;
1091
1100template <typename T, size_t N>
1102
1110template <typename R>
1112
1117template <typename T>
1118constexpr bool is_1d_index_range_v = false;
1119
1124template <typename T>
1126
1137template <typename R>
1139
1144template <typename T>
1145constexpr bool is_md_index_range_v = false;
1146
1152template <typename T, size_t N>
1153requires (N > 1)
1155
1167template <typename R>
1169
1170} // end of namespace tf -----------------------------------------------------
class to create an N-dimensional index range of integral indices
Definition iterator.hpp:165
const std::array< IndexRange< T, 1 >, N > & dims() const
Definition iterator.hpp:355
IndexRange(const std::array< IndexRange< T, 1 >, N > &dims)
constructs an N-D index range from an array of 1D ranges
Definition iterator.hpp:656
T index_type
alias for the index type
Definition iterator.hpp:172
size_t size() const
returns the number of active flat iterations
Definition iterator.hpp:659
IndexRange()=default
constructs an N-dimensional index range without initialization
std::pair< IndexRange< T, N >, size_t > slice_floor(size_t flat_beg, size_t chunk_size) const
returns the largest perfectly-aligned hyperbox reachable from flat_beg whose size does not exceed chu...
Definition iterator.hpp:793
IndexRange(Ranges &&... ranges)
constructs an N-D index range from N 1D IndexRange<T, 1> objects
Definition iterator.hpp:230
const IndexRange< T, 1 > & dim(size_t d) const
returns the 1D range for dimension d (read-only)
Definition iterator.hpp:298
static constexpr size_t rank
the number of dimensions (always 1 for this specialization)
Definition iterator.hpp:925
IndexRange< T, 1 > & step_size(T new_step_size)
updates the step size of the range
Definition iterator.hpp:979
IndexRange< T, 1 > & end(T new_end)
updates the ending index of the range
Definition iterator.hpp:974
T begin() const
queries the starting index of the range
Definition iterator.hpp:944
IndexRange()=default
constructs an index range object without any initialization
size_t floor(size_t chunk_size) const
returns the largest hyperplane-aligned chunk size that is <= chunk_size
Definition iterator.hpp:692
static constexpr size_t rank
the number of dimensions
Definition iterator.hpp:177
T step_size() const
queries the step size of the range
Definition iterator.hpp:954
IndexRange< T, 1 > & begin(T new_begin)
updates the starting index of the range
Definition iterator.hpp:969
T index_type
alias for the index type used in the range
Definition iterator.hpp:920
size_t size(size_t d) const
returns the number of iterations along dimension d
Definition iterator.hpp:396
IndexRange(T beg, T end, T step_size)
constructs an IndexRange object
Definition iterator.hpp:938
T end() const
queries the ending index of the range
Definition iterator.hpp:949
std::pair< IndexRange< T, N >, size_t > slice_ceil(size_t flat_beg, size_t chunk_size) const
returns the smallest perfectly-aligned hyperbox reachable from flat_beg, rounding up to the next hype...
Definition iterator.hpp:717
IndexRange< T, 1 > & dim(size_t d)
returns the 1D range for dimension d (mutable)
Definition iterator.hpp:325
size_t ceil(size_t chunk_size) const
returns the smallest hyperplane-aligned chunk size that is >= chunk_size, capped at size() when chunk...
Definition iterator.hpp:670
IndexRange< T, 1 > & reset(T begin, T end, T step_size)
updates the range with the new starting index, ending index, and step size
Definition iterator.hpp:959
concept to check if a type is a tf::IndexRange<T, 1>.
Definition iterator.hpp:1138
concept to check if a type an tf::IndexRange, regardless of dimensionality
Definition iterator.hpp:1111
concept to check if a type is a tf::IndexRange<T, N> with rank > 1
Definition iterator.hpp:1168
taskflow namespace
Definition small_vector.hpp:20
constexpr bool is_1d_index_range_v
base type trait to detect if a type is a 1D IndexRange
Definition iterator.hpp:1118
constexpr bool is_md_index_range_v
base type trait to detect if a type is a multi-dimensional IndexRange (rank > 1)
Definition iterator.hpp:1145
constexpr bool is_index_range_v
base type trait to detect if a type is an IndexRange
Definition iterator.hpp:1090
constexpr size_t distance(T beg, T end, T step)
calculates the number of iterations in the given index range
Definition iterator.hpp:71
constexpr bool is_index_range_invalid(T beg, T end, T step)
checks if the given index range is invalid
Definition iterator.hpp:28
IndexRange(T, T, T) -> IndexRange< T, 1 >
deduction guide for tf::IndexRange<T, 1>