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 // Pass 1 (forward): cache dim sizes and find d_active — the first zero-size
724 // dimension. Only [0, d_active) participates in the flat iteration space;
725 // dims [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 };
734
735 // Pass 2a (backward): decode flat_beg into per-dimension coords.
736 // Must complete fully — all coords needed for box construction.
737 size_t coords[N] = {};
738 size_t temp = flat_beg;
739 for (size_t d = d_active; d-- > 0; ) {
740 coords[d] = temp % dim_sizes[d];
741 temp /= dim_sizes[d];
742 }
743
744 // Pass 2b (backward, fused with grow_dim search): find grow_dim.
745 // Ceil variant: commit grow_dim BEFORE the budget check (round up).
746 size_t grow_dim = d_active - 1;
747 size_t inner_volume = 1;
748 size_t active_inner_vol = 1;
749
750 for (size_t d = d_active; d-- > 0; ) {
751 // trailing-zeros rule: inner coord non-zero → can't expand further out
752 if (d + 1 < d_active && coords[d + 1] != 0) break;
753
754 // ceil: commit BEFORE budget check
755 grow_dim = d;
756 active_inner_vol = inner_volume;
757
758 if (inner_volume >= chunk_size) break;
759
760 inner_volume *= dim_sizes[d];
761 }
762
763 // Steps along grow_dim: ceil division, at least 1 for forward progress.
764 size_t steps_left = dim_sizes[grow_dim] - coords[grow_dim];
765 size_t steps_needed = (std::max)(size_t{1}, chunk_size / active_inner_vol);
766 size_t steps_to_take = (std::min)(steps_left, steps_needed);
767
768 // Pass 3: construct the sub-box in three clean segments with no branching
769 // inside each loop:
770 // [0, grow_dim) — locked dims (one element each)
771 // [grow_dim] — the grow dimension
772 // [grow_dim+1, N) — full inner/inactive extent
773 std::array<IndexRange<T, 1>, N> box_dims;
774
775 for (size_t d = 0; d < grow_dim; ++d) {
776 const T beg = _dims[d].begin();
777 const T step = _dims[d].step_size();
778 box_dims[d] = IndexRange<T, 1>(
779 beg + static_cast<T>(coords[d]) * step,
780 beg + static_cast<T>(coords[d] + 1) * step,
781 step
782 );
783 }
784
785 {
786 const T beg = _dims[grow_dim].begin();
787 const T step = _dims[grow_dim].step_size();
788 box_dims[grow_dim] = IndexRange<T, 1>(
789 beg + static_cast<T>(coords[grow_dim]) * step,
790 beg + static_cast<T>(coords[grow_dim] + steps_to_take) * step,
791 step
792 );
793 }
794
795 for (size_t d = grow_dim + 1; d < N; ++d) {
796 box_dims[d] = _dims[d]; // full inner or inactive extent
797 }
798
799 return { IndexRange<T, N>(box_dims), steps_to_take * active_inner_vol };
800}
801
802template <std::integral T, size_t N>
803std::pair<IndexRange<T, N>, size_t>
804IndexRange<T, N>::slice_floor(size_t flat_beg, size_t chunk_size) const {
805
806 if (chunk_size == 0) {
807 return { *this, 0 };
808 }
809
810 // Pass 1 (forward): cache dim sizes and find d_active.
811 size_t dim_sizes[N];
812 size_t d_active = N;
813 for (size_t d = 0; d < N; ++d) {
814 dim_sizes[d] = _dims[d].size();
815 if (dim_sizes[d] == 0) { d_active = d; break; }
816 }
817
818 if (d_active == 0) return { *this, 0 };
819
820 // Pass 2a (backward): decode flat_beg into per-dimension coords.
821 // Must complete fully — all coords needed for box construction.
822 size_t coords[N] = {};
823 size_t temp = flat_beg;
824 for (size_t d = d_active; d-- > 0; ) {
825 coords[d] = temp % dim_sizes[d];
826 temp /= dim_sizes[d];
827 }
828
829 // Pass 2b (backward, fused with grow_dim search): find grow_dim.
830 // Floor variant: commit grow_dim AFTER the budget check (round down).
831 size_t grow_dim = d_active - 1;
832 size_t inner_volume = 1;
833 size_t active_inner_vol = 1;
834
835 for (size_t d = d_active; d-- > 0; ) {
836 // trailing-zeros rule
837 if (d + 1 < d_active && coords[d + 1] != 0) break;
838
839 // floor: budget check BEFORE committing
840 size_t next_vol = inner_volume * dim_sizes[d];
841 if (next_vol > chunk_size && inner_volume > 1) break;
842
843 grow_dim = d;
844 active_inner_vol = inner_volume;
845 inner_volume = next_vol;
846 }
847
848 // Steps along grow_dim: floor division, at least 1 for forward progress.
849 size_t steps_left = dim_sizes[grow_dim] - coords[grow_dim];
850 size_t steps_needed = (std::max)(size_t{1}, chunk_size / active_inner_vol);
851 size_t steps_to_take = (std::min)(steps_left, steps_needed);
852
853 // Pass 3: construct the sub-box in three clean segments.
854 std::array<IndexRange<T, 1>, N> box_dims;
855
856 for (size_t d = 0; d < grow_dim; ++d) {
857 const T beg = _dims[d].begin();
858 const T step = _dims[d].step_size();
859 box_dims[d] = IndexRange<T, 1>(
860 beg + static_cast<T>(coords[d]) * step,
861 beg + static_cast<T>(coords[d] + 1) * step,
862 step
863 );
864 }
865
866 {
867 const T beg = _dims[grow_dim].begin();
868 const T step = _dims[grow_dim].step_size();
869 box_dims[grow_dim] = IndexRange<T, 1>(
870 beg + static_cast<T>(coords[grow_dim]) * step,
871 beg + static_cast<T>(coords[grow_dim] + steps_to_take) * step,
872 step
873 );
874 }
875
876 for (size_t d = grow_dim + 1; d < N; ++d) {
877 box_dims[d] = _dims[d]; // full inner or inactive extent
878 }
879
880 return { IndexRange<T, N>(box_dims), steps_to_take * active_inner_vol };
881}
882
883
884// ============================================================================
885// Partial specialization: IndexRange<T, 1> (1D case)
886//
887// This is the original IndexRange<T> class, re-expressed as the N=1
888// specialization so that IndexRange<int, 1> and IndexRange<int> (via the
889// default argument N=1 on the primary template forward declaration above) both
890// refer to the same type.
891// ============================================================================
892
928template <std::integral T>
929class IndexRange<T, 1> {
930
931 public:
932
936 using index_type = T;
937
941 static constexpr size_t rank = 1;
942
946 IndexRange() = default;
947
954 explicit IndexRange(T beg, T end, T step_size)
955 : _beg{beg}, _end{end}, _step_size{step_size} {}
956
960 T begin() const { return _beg; }
961
965 T end() const { return _end; }
966
970 T step_size() const { return _step_size; }
971
975 IndexRange<T, 1>& reset(T begin, T end, T step_size) {
976 _beg = begin;
977 _end = end;
978 _step_size = step_size;
979 return *this;
980 }
981
985 IndexRange<T, 1>& begin(T new_begin) { _beg = new_begin; return *this; }
986
990 IndexRange<T, 1>& end(T new_end) { _end = new_end; return *this; }
991
995 IndexRange<T, 1>& step_size(T new_step_size) { _step_size = new_step_size; return *this; }
996
1009 size_t size() const;
1010
1043 IndexRange<T, 1> unravel(size_t part_beg, size_t part_end) const;
1044
1045 private:
1046
1047 T _beg;
1048 T _end;
1049 T _step_size;
1050
1051};
1052
1053template <std::integral T>
1055 return distance(_beg, _end, _step_size);
1056}
1057
1058template <std::integral T>
1059IndexRange<T, 1> IndexRange<T, 1>::unravel(size_t part_beg, size_t part_end) const {
1060 return IndexRange<T, 1>(
1061 static_cast<T>(part_beg) * _step_size + _beg,
1062 static_cast<T>(part_end) * _step_size + _beg,
1063 _step_size
1064 );
1065}
1066
1067// ----------------------------------------------------------------------------
1068// Deduction guide: IndexRange(beg, end, step) -> IndexRange<T, 1>
1069//
1070// Required because IndexRange is now a two-parameter template (T, N).
1071// Without this guide, CTAD cannot deduce N from a three-argument constructor
1072// call such as `tf::IndexRange range(0, 10, 2)`.
1073// ----------------------------------------------------------------------------
1074
1094template <std::integral T>
1096
1097// ==========================================
1098// traits
1099// ==========================================
1100
1105template <typename>
1106constexpr bool is_index_range_v = false;
1107
1116template <typename T, size_t N>
1118
1126template <typename R>
1128
1133template <typename T>
1134constexpr bool is_1d_index_range_v = false;
1135
1140template <typename T>
1142
1153template <typename R>
1155
1160template <typename T>
1161constexpr bool is_md_index_range_v = false;
1162
1168template <typename T, size_t N>
1169requires (N > 1)
1171
1183template <typename R>
1185
1186} // end of namespace tf -----------------------------------------------------
IndexRange< T, 1 > & step_size(T new_step_size)
updates the step size of the range
Definition iterator.hpp:995
IndexRange< T, 1 > & end(T new_end)
updates the ending index of the range
Definition iterator.hpp:990
T begin() const
queries the starting index of the range
Definition iterator.hpp:960
IndexRange()=default
constructs an index range object without any initialization
T step_size() const
queries the step size of the range
Definition iterator.hpp:970
IndexRange< T, 1 > & begin(T new_begin)
updates the starting index of the range
Definition iterator.hpp:985
T index_type
alias for the index type used in the range
Definition iterator.hpp:936
IndexRange(T beg, T end, T step_size)
constructs an IndexRange object
Definition iterator.hpp:954
T end() const
queries the ending index of the range
Definition iterator.hpp:965
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:975
class to create an N-dimensional index range of integral indices
Definition iterator.hpp:165
const std::array< IndexRange< T, 1 >, N > & dims() const
returns the underlying array of all per-dimension 1D ranges
Definition iterator.hpp:355
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:804
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
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
size_t size(size_t d) const
returns the number of iterations along dimension d
Definition iterator.hpp:396
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
concept to check if a type is a tf::IndexRange<T, 1>.
Definition iterator.hpp:1154
concept to check if a type an tf::IndexRange, regardless of dimensionality
Definition iterator.hpp:1127
concept to check if a type is a tf::IndexRange<T, N> with rank > 1
Definition iterator.hpp:1184
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:1134
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:1161
constexpr bool is_index_range_v
base type trait to detect if a type is an IndexRange
Definition iterator.hpp:1106
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>