diff --git a/src/TiledIteration.jl b/src/TiledIteration.jl index 423e76b..60cd3d6 100644 --- a/src/TiledIteration.jl +++ b/src/TiledIteration.jl @@ -11,10 +11,11 @@ else _inc(state, iter) = inc(state, iter.indices) end -export TileIterator, TiledUnitRange, TiledIndices, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile +export TileIterator, FixedTileRange, FixedTile, TileIndices, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile include("tileiterator.jl") -include("tile.jl") +include("tilerange.jl") +include("tileindices.jl") const L1cachesize = 2^15 const cachelinesize = 64 diff --git a/src/tile.jl b/src/tile.jl deleted file mode 100644 index b902710..0000000 --- a/src/tile.jl +++ /dev/null @@ -1,73 +0,0 @@ -### TiledUnitRange - -struct TiledUnitRange{T, R} <: AbstractUnitRange{T} - parent::R - tilelength::T - tilestride::T - length::T - - function TiledUnitRange{T, R}(parent::R, tilelength::T, tilestride::T) where {T, R} - n = _length(last(parent), tilelength, tilestride) - new{T, R}(parent, tilelength, tilestride, n) - end -end -TiledUnitRange(parent::R, l::T, s::T) where {T, R} = TiledUnitRange{T, R}(parent, l, s) -TiledUnitRange(parent, tilelength) = TiledUnitRange(parent, tilelength, tilelength) - -_length(stop::T, n, Δ) where T = ceil(T, (stop - n)/Δ) + 1 -_length(stop::CartesianIndex{1}, n, Δ) = _length(first(stop.I), n, Δ) - -tilelength(r::TiledUnitRange{T, R}) where {T, R<:CartesianIndices} = CartesianIndex(r.tilelength) -tilelength(r::TiledUnitRange) = r.tilelength - -tilestride(r::TiledUnitRange{T, R}) where {T, R<:CartesianIndices} = CartesianIndex(r.tilestride) -tilestride(r::TiledUnitRange) = r.tilestride - -Base.length(r::TiledUnitRange) = r.length - -function Base.first(r::TiledUnitRange{T, R}) where {T, R} - start = first(r.parent) - stop = min(start+tilelength(r), last(r.parent)) - return start:stop -end - -function Base.last(r::TiledUnitRange{T, R}) where {T, R} - start = first(r.parent) + (length(r)-1) * tilestride(r) - stop = min(start+tilelength(r), last(r.parent)) - return start:stop -end - -function Base.getindex(r::TiledUnitRange{T, R}, i::Int) where {T, R} - start = first(r.parent) + (i-1)*tilestride(r) - stop = min(start+tilelength(r), last(r.parent)) - return start:stop -end - -Base.show(io::IO, r::TiledUnitRange) = print(io, "TiledUnitRange(", r.parent, ",", r.tilelength, ",", r.tilestride, ")") -function Base.show(io::IO, r::TiledUnitRange{T, R}) where {T, R<:CartesianIndices} - print(io, "TiledUnitRange(CartesianIndices(", r.parent.indices, "),", r.tilelength, ",", r.tilestride, ")") -end - -### TiledIndices - -struct TiledIndices{N, T, R} <: AbstractArray{R, N} - indices::NTuple{N, TiledUnitRange{T, R}} -end - -TiledIndices(indices, tilelength) = TiledIndices(indices, tilelength, tilelength) -TiledIndices(indices, tilelength, tilestride) = - TiledIndices(map(TiledUnitRange, indices, tilelength, tilestride)) -TiledIndices(indices::CartesianIndices, tilelength, tilestride) = - TiledIndices(map(CartesianIndices, indices.indices), tilelength, tilestride) - -Base.size(iter::TiledIndices) = map(length, iter.indices) -Base.@propagate_inbounds Base.getindex(iter::TiledIndices{N}, inds::Vararg{Int, N}) where N = map(getindex, iter.indices, inds) -Base.@propagate_inbounds function Base.getindex( - iter::TiledIndices{N, T, R}, - inds::Vararg{Int, N}) where {N, T, R<:CartesianIndices} - tile = map(getindex, iter.indices, inds) - # reformulate into CartesianIndices{N} - CartesianIndices(mapreduce(I->I.indices, (i,j)->(i..., j...), tile)) -end - - diff --git a/src/tileindices.jl b/src/tileindices.jl new file mode 100644 index 0000000..3571643 --- /dev/null +++ b/src/tileindices.jl @@ -0,0 +1,83 @@ +### TileIndices + +struct TileIndices{T, N, R<:AbstractTileRange} <: AbstractArray{T, N} + indices::NTuple{N, R} + function TileIndices(indices::NTuple{N, <:AbstractTileRange{T}}) where {N, T} + new{NTuple{N, T}, N, eltype(indices)}(indices) + end +end + +""" + TileIndices(indices, sz, Δ=sz; keep_last=true) + +Construct a sliding tile along axes `r` with fixed sliding strides `Δ` and tile size `sz`. + +# Arguments + +- `sz`: The size of each tile. If keyword `keep_last=true`, the last tile size might be smaller than + `sz`. +- `Δ=sz`: For each dimension `i` and `r = indices[i]`, the sliding stride `Δ[i]` is defined as + `first(r[n]) - first(r[n-1])`. Using a stride `Δ[i] < sz[i]` means there are overlaps between each + adjacent tile along this dimension. +- `keep_last=true` (keyword): this keyword affects the cases when the last tile size is smaller + than `sz`, in which case, `true`/`false` tells `TileIndices` to keep/discard the last tile. + +# Examples + +```jldoctest +julia> TileIndices((1:4, 0:5), (3, 4), (2, 3)) + 2×2 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}}: + (1:3, 0:3) (1:3, 3:5) + (3:4, 0:3) (3:4, 3:5) + +julia> TileIndices((1:4, 0:5), (3, 4), (2, 3); keep_last=false) +1×1 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{false}, UnitRange{Int64}}}: + (1:3, 0:3) +``` + +When `sz` and `Δ` are scalars, it affects each dimension equivalently. + +```jldoctest +julia> TileIndices((1:4, 0:5), 3, 2) +2×3 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}}: + (1:3, 0:2) (1:3, 2:4) (1:3, 4:5) + (3:4, 0:2) (3:4, 2:4) (3:4, 4:5) + +julia> TileIndices((1:4, 0:5), 3, 2; keep_last=false) +1×2 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{false}, UnitRange{Int64}}}: + (1:3, 0:2) (1:3, 2:4) +``` + +!!! note + This method is equivalent to `TileIndices(indices, FixedTile(sz, Δ; keep_last=keep_last))`. +""" +TileIndices(indices, n, Δ=n; kwargs...) = TileIndices(indices, FixedTile(n, Δ; kwargs...)) + + +""" + TileIndices(indices, s::AbstractTileStrategy) + +Construct a sliding tile along axes `r` using provided tile strategy `s`. + +Currently available strategies are: + +- [`FixedTile`](@ref): each tile is of the same fixed size (except the last one). + +For usage examples, please refer to the docstring of each tile strategy. +""" +TileIndices(indices, s::AbstractTileStrategy) = TileIndices(s(indices)) + +Base.size(iter::TileIndices) = map(length, iter.indices) + +Base.@propagate_inbounds function Base.getindex( + iter::TileIndices{T, N}, + inds::Vararg{Int, N}) where {N, T} + map(getindex, iter.indices, inds) +end +Base.@propagate_inbounds function Base.getindex( + iter::TileIndices{T, N}, + inds::Vararg{Int, N}) where {N, T<:NTuple{N, CartesianIndices}} + tile = map(getindex, iter.indices, inds) + # reformulate into CartesianIndices{N} + CartesianIndices(mapreduce(I->I.indices, (i,j)->(i..., j...), tile)) +end diff --git a/src/tilerange.jl b/src/tilerange.jl new file mode 100644 index 0000000..8055ab1 --- /dev/null +++ b/src/tilerange.jl @@ -0,0 +1,197 @@ +# AbstractTileRange Protocols +# +# The parent range `R` is supposed to be an array of scalar indices, including: +# - ranges +# - vectors of integers +# - CartesianIndices{1} +# +# A tile range generated from `R` is a iterator whose elements are also an array of scalar indices. +# It's an indices-specific block array. +# +# Reference: https://docs.julialang.org/en/v1.5/manual/arrays/#man-supported-index-types +# +# +# | scalar index | indices(tile) | +# |-------------------|----------------- | +# | range/vector | AbstractTileRange | +# | CartesianIndices | TileIndices | +# +# AbstractTileStrategy serves as an adapter for `AbstractTileRange` and `TileIndices` + + +abstract type AbstractTileRange{R} <: AbstractArray{R, 1} end +abstract type AbstractTileStrategy end + +const Range1 = Union{AbstractUnitRange, AbstractVector} + +### FixedTileRange and FixedTile + +""" + FixedTileRange(r, n, [Δ=n]; keep_last=true) + +Construct a sliding tile along range `r` with fixed sliding stride `Δ` and tile length `n`. + +# Arguments + +- `r`: a range, `CartesianIndices` or `Vector` +- `n::Integer`: The length of each tile. If keyword `keep_last=true`, the last tile length might be + less than `n`. +- `Δ::Union{Integer, CartesianIndex{1}}=n`: The sliding stride `Δ` is defined as `first(r[n]) - + first(r[n-1])`. Using a stride `Δ FixedTileRange(2:10, 3) +3-element FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}: + 2:4 + 5:7 + 8:10 + +julia> FixedTileRange(1:10, 4) +3-element FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}: + 1:4 + 5:8 + 9:10 + +julia> FixedTileRange(1:10, 4, 2) + 4-element FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}: + 1:4 + 3:6 + 5:8 + 7:10 + +julia> FixedTileRange(1:10, 4; keep_last=false) + 2-element FixedTileRange{UnitRange{Int64}, Int64, Val{false}, UnitRange{Int64}}: + 1:4 + 5:8 +``` + +Besides an `AbstractUnitRange`, the input range `r` can also be a `CartesianIndices{1}` or more +generally, an `AbstractVector{<:Integer}`: + +```jldoctest +julia> FixedTileRange(CartesianIndices((1:10, )), 4) + 3-element FixedTileRange{CartesianIndices{1, Tuple{UnitRange{Int64}}}, Int64, Val{true}, CartesianIndices{1, Tuple{UnitRange{Int64}}}}: + [CartesianIndex(1,), CartesianIndex(2,), CartesianIndex(3,), CartesianIndex(4,)] + [CartesianIndex(5,), CartesianIndex(6,), CartesianIndex(7,), CartesianIndex(8,)] + [CartesianIndex(9,), CartesianIndex(10,)] +``` + +!!! warning It usually has bad indexing performance if `r` is not lazily evaluated. For example, + `FixedTileRange(collect(1:10), 4)` creates a new `Vector` of length `4` everytime when + `getindex` is called. +""" +struct FixedTileRange{R, T, RP} <: AbstractTileRange{R} + parent::RP + n::T + Δ::T + keep_last::Bool + + # keep `length` information to avoid unnecessary calculation and thus is more performant + length::T + + function FixedTileRange(parent::R, n::T, Δ; keep_last::Bool=true) where {R<:Range1, T} + _length = _fixedtile_length(first(parent), last(parent), n, Δ, keep_last) + new{_eltype(R), T, R}(parent, n, Δ, keep_last, _length) + end +end +FixedTileRange(r::Range1, n::Integer; kwargs...) = FixedTileRange(r, n, n; kwargs...) + +_eltype(::Type{R}) where R<:AbstractUnitRange = UnitRange{eltype(R)} +_eltype(::Type{R}) where R<:AbstractVector = R # this includes CartesianIndices{1} + +_int(x::Integer) = x +_int(x::CartesianIndex{1}) = first(x.I) + +function _fixedtile_length(start::T, stop::T, n, Δ, keep_last) where T<:Integer + _round = keep_last ? ceil : floor + return _round(T, (stop - n - start + 1)/_int(Δ)) + 1 +end +_fixedtile_length(start::T, stop::T, n, Δ, keep_last) where T<:CartesianIndex{1} = + _fixedtile_length(_int(start), _int(stop), n, Δ, keep_last) + +"Get the length of the tile. The last tile might has few elements than this." +tilelength(r::FixedTileRange{<:CartesianIndices{1}}) = CartesianIndex{1}(r.n) +tilelength(r::FixedTileRange) = r.n + +"Get the stride between adjacent tiles." +tilestride(r::FixedTileRange{<:CartesianIndices{1}}) = CartesianIndex{1}(r.Δ) +tilestride(r::FixedTileRange) = r.Δ + +Base.size(r::FixedTileRange) = (r.length, ) +Base.length(r::FixedTileRange) = r.length + +Base.@propagate_inbounds function Base.getindex(r::FixedTileRange{R, T}, i::Int) where {R, T} + @boundscheck checkbounds(r, i) + start = first(r.parent) + (i-1)*tilestride(r) + stop = min(start+tilelength(r)-oneunit(eltype(R)), last(r.parent)) + return convert(R, start:stop) +end + + +""" + FixedTile(sz, Δ; keep_last=true) + +A tile strategy that you can use to construct [`TileIndices`](@ref) using [`FixedTileRange`](@ref). + +# Arguments + +- `sz`: The size of each tile. If keyword `keep_last=true`, the last tile size might be smaller than + `sz`. +- `Δ=sz`: For each dimension `i` and `r = indices[i]`, the sliding stride `Δ[i]` is defined as + `first(r[n]) - first(r[n-1])`. Using a stride `Δ[i] < sz[i]` means there are overlaps between each + adjacent tile along this dimension. +- `keep_last=true` (keyword): this keyword affects the cases when the last tile size is smaller + than `sz`, in which case, `true`/`false` tells `TileIndices` to keep/discard the last tile. + +# Examples + +```jldoctest +julia> TileIndices((1:4, 0:5), FixedTile((3, 4), (2, 3))) + 2×2 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}}: + (1:3, 0:3) (1:3, 3:5) + (3:4, 0:3) (3:4, 3:5) + +julia> TileIndices((1:4, 0:5), FixedTile((3, 4), (2, 3); keep_last=false)) +1×1 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{false}, UnitRange{Int64}}}: + (1:3, 0:3) +``` + +When `sz` and `Δ` are scalars, it affects each dimension equivalently. + +```jldoctest +julia> TileIndices((1:4, 0:5), FixedTile(3, 2)) +2×3 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}}: + (1:3, 0:2) (1:3, 2:4) (1:3, 4:5) + (3:4, 0:2) (3:4, 2:4) (3:4, 4:5) + +julia> TileIndices((1:4, 0:5), FixedTile(3, 2; keep_last=false)) +1×2 TileIndices{Tuple{UnitRange{Int64}, UnitRange{Int64}}, 2, FixedTileRange{UnitRange{Int64}, Int64, Val{false}, UnitRange{Int64}}}: + (1:3, 0:2) (1:3, 2:4) +``` +""" +struct FixedTile{N, T} <: AbstractTileStrategy + size::T + Δ::T + keep_last::Bool +end + +FixedTile(sz::T, Δ=sz; keep_last=true) where T<:Integer = FixedTile{0, T}(sz, Δ, keep_last) +FixedTile(sz::T, Δ=sz; keep_last=true) where T = FixedTile{length(sz), T}(sz, Δ, keep_last) + +(S::FixedTile{0})(r::Range1) = FixedTileRange(r, S.size, S.Δ; keep_last=S.keep_last) +(S::FixedTile{0})(r::CartesianIndices{1}) = FixedTileRange(r, S.size, S.Δ; keep_last=S.keep_last) +(S::FixedTile{0})(indices) = + map(r->FixedTileRange(r, S.size, S.Δ; keep_last=S.keep_last), indices) + +(S::FixedTile{N})(indices) where N = + map((args...)->FixedTileRange(args...; keep_last=S.keep_last), indices, S.size, S.Δ) +# ambiguity patch +(S::FixedTile{0})(indices::CartesianIndices{N}) where N = + S(map(x->CartesianIndices((x, )), indices.indices)) +(S::FixedTile{N})(indices::CartesianIndices{N}) where N = + S(map(x->CartesianIndices((x, )), indices.indices)) diff --git a/test/runtests.jl b/test/runtests.jl index 6d7701c..dcf2e79 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,8 @@ if VERSION < v"1.6-" # Documenter.doctest(TiledIteration, doctestfilters = doctestfilters) end +include("tilerange.jl") +include("tileindices.jl") @testset "TileIterator small examples" begin titr = @inferred TileIterator((1:10,), RelaxLastTile((3,))) diff --git a/test/tileindices.jl b/test/tileindices.jl new file mode 100644 index 0000000..19c6ed5 --- /dev/null +++ b/test/tileindices.jl @@ -0,0 +1,87 @@ +@testset "TileIndices" begin + function test_iteration(R) + for (i, v) in enumerate(R) + @test v == R[i] + end + end + + @testset "FixedTile" begin + @testset "axes" begin + R0 = (1:4, 0:5) + + R = @inferred TileIndices(R0, FixedTile((3, 4), (2, 3))) + @test R == TileIndices(R0, (3, 4), (2, 3)) + @test R isa AbstractArray + @test eltype(R) == Tuple{UnitRange{Int}, UnitRange{Int}} + @test size(R) == (2, 2) + @test R[1] == (1:3, 0:3) + @test R[end] == (3:4, 3:5) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile((3, 4), (2, 3), keep_last=false)) + @test R == TileIndices(R0, (3, 4), (2, 3), keep_last=false) + @test R isa AbstractArray + @test eltype(R) == Tuple{UnitRange{Int}, UnitRange{Int}} + @test size(R) == (1, 1) + @test R[1] == (1:3, 0:3) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile(3, 2)) + @test R == TileIndices(R0, 3, 2) + @test R isa AbstractArray + @test eltype(R) == Tuple{UnitRange{Int}, UnitRange{Int}} + @test size(R) == (2, 3) + @test R[1] == R[1, 1] == (1:3, 0:2) + @test R[3] == R[1, 2] == (1:3, 2:4) + @test R[end] == R[6] == (3:4, 4:5) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile(3, 2, keep_last=false)) + @test R == TileIndices(R0, 3, 2, keep_last=false) + @test R isa AbstractArray + @test eltype(R) == Tuple{UnitRange{Int}, UnitRange{Int}} + @test size(R) == (1, 2) + @test R[1] == R[1, 1] == (1:3, 0:2) + @test R[2] == R[1, 2] == R[end] == (1:3, 2:4) + end + + @testset "CartesianIndices" begin + R0 = CartesianIndex(1,0):CartesianIndex(4, 5) + + R = @inferred TileIndices(R0, FixedTile((3, 4), (2, 3))) + @test R == TileIndices(R0, (3, 4), (2, 3)) + @test R isa AbstractArray + @test eltype(R) <: Tuple{<:CartesianIndices, <:CartesianIndices} + @test size(R) == (2, 2) + @test R[1] == CartesianIndices((1:3, 0:3)) + @test R[end] == CartesianIndices((3:4, 3:5)) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile((3, 4), (2, 3), keep_last=false)) + @test R == TileIndices(R0, (3, 4), (2, 3), keep_last=false) + @test R isa AbstractArray + @test eltype(R) <: Tuple{<:CartesianIndices, <:CartesianIndices} + @test size(R) == (1, 1) + @test R[1] == CartesianIndices((1:3, 0:3)) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile(3, 2)) + @test R == TileIndices(R0, 3, 2) + @test R isa AbstractArray + @test eltype(R) <: Tuple{<:CartesianIndices, <:CartesianIndices} + @test size(R) == (2, 3) + @test R[1] == R[1, 1] == CartesianIndices((1:3, 0:2)) + @test R[3] == R[1, 2] == CartesianIndices((1:3, 2:4)) + @test R[end] == R[6] == CartesianIndices((3:4, 4:5)) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile(3, 2, keep_last=false)) + @test R == TileIndices(R0, 3, 2, keep_last=false) + @test R isa AbstractArray + @test eltype(R) <: Tuple{<:CartesianIndices, <:CartesianIndices} + @test size(R) == (1, 2) + @test R[1] == R[1, 1] == CartesianIndices((1:3, 0:2)) + @test R[2] == R[1, 2] == R[end] == CartesianIndices((1:3, 2:4)) + end + end +end diff --git a/test/tilerange.jl b/test/tilerange.jl new file mode 100644 index 0000000..068eefe --- /dev/null +++ b/test/tilerange.jl @@ -0,0 +1,198 @@ +@testset "Fixed Tile" begin + @testset "FixedTileRange" begin + function test_iteration(r) + for (i, v) in enumerate(r) + @test v == r[i] + end + @test collect(r) == r + + rst = zero(first(r.parent)) + for v in r + rst += sum(v) + end + @test mapreduce(sum, +, r) == rst + end + + @testset "ranges" begin + for r0 in [ + 2:10, + UInt8(2):UInt8(10), + Base.IdentityUnitRange(2:10), + OffsetArrays.IdOffsetRange(2:10), + OffsetArrays.IdOffsetRange(Base.OneTo(9), 1) + ] + # keep the last tile + r = @inferred FixedTileRange(r0, 4) + @test r == FixedTileRange(r0, 4; keep_last=true) == FixedTileRange(r0, 4, 4) + @test r isa AbstractArray + @test eltype(r) == UnitRange{eltype(r0)} + @test length(r) == 3 + @test first(r) == 2:5 + @test last(r) == 10:10 + test_iteration(r) + @test all(map(x->length(x)==4, r[1:end-1])) + @test length(r[end]) <= 4 + + # different stride + r = @inferred FixedTileRange(r0, 4, 2) + @test r == FixedTileRange(r0, 4, 2; keep_last=true) + @test r isa AbstractArray + @test eltype(r) == UnitRange{eltype(r0)} + @test length(r) == 4 + @test first(r) == 2:5 + @test last(r) == 8:10 + test_iteration(r) + @test all(map(x->length(x)==4, r[1:end-1])) + @test length(r[end]) <= 4 + + # discard the last tile + r = @inferred FixedTileRange(r0, 4, keep_last=false) + @test r isa AbstractArray + @test eltype(r) == UnitRange{eltype(r0)} + @test length(r) == 2 + @test first(r) == 2:5 + @test last(r) == 6:9 + test_iteration(r) + @test all(map(x->length(x)==4, r)) + end + + # FixedTileRange only works for 1d case + @test_throws MethodError FixedTileRange((2:10, 2:10), (4, 4), (2, 2)) + end + + @testset "CartesianIndices" begin + r0 = CartesianIndex(2):CartesianIndex(10) + + # keep the last tile + r = @inferred FixedTileRange(r0, 4) + @test r == FixedTileRange(r0, 4; keep_last=true) == FixedTileRange(r0, 4, 4) + @test r isa AbstractArray + @test eltype(r) == typeof(r0) + @test length(r) == 3 + @test first(r) == CartesianIndex(2):CartesianIndex(5) + @test last(r) == CartesianIndex(10):CartesianIndex(10) + test_iteration(r) + @test all(map(x->length(x)==4, r[1:end-1])) + @test length(r[end]) <= 4 + + # `Δ` can also be `CartesianIndex` when `r` is a `CartesianIndices` + @test FixedTileRange(r0, 4, 2) == FixedTileRange(r0, 4, CartesianIndex(2)) + + # different stride + r = @inferred FixedTileRange(r0, 4, 2) + @test r == FixedTileRange(r0, 4, 2; keep_last=true) + @test r isa AbstractArray + @test eltype(r) == typeof(r0) + @test length(r) == 4 + @test first(r) == CartesianIndex(2):CartesianIndex(5) + @test last(r) == CartesianIndex(8):CartesianIndex(10) + test_iteration(r) + @test all(map(x->length(x)==4, r[1:end-1])) + @test length(r[end]) <= 4 + + # discard the last tile + r = @inferred FixedTileRange(r0, 4, keep_last=false) + @test r isa AbstractArray + @test eltype(r) == typeof(r0) + @test length(r) == 2 + @test first(r) == CartesianIndex(2):CartesianIndex(5) + @test last(r) == CartesianIndex(6):CartesianIndex(9) + # test_iteration(r) + @test all(map(x->length(x)==4, r)) + end + + @testset "AbstractVector" begin + # AbstractVector can be terrible for performance if it allocates memory. + + for r0 in [ + collect(2:10), + collect(UInt8(2):UInt8(10)), + # Broken: https://github.com/JuliaArrays/OffsetArrays.jl/issues/171 + # OffsetArray(collect(2:10), -1) + ] + # keep the last tile + r = @inferred FixedTileRange(r0, 4) + @test r == FixedTileRange(r0, 4; keep_last=true) == FixedTileRange(r0, 4, 4) + @test r isa AbstractArray + @test eltype(r) == Vector{eltype(r0)} + @test length(r) == 3 + @test first(r) == collect(2:5) + @test last(r) == collect(10:10) + test_iteration(r) + @test all(map(x->length(x)==4, r[1:end-1])) + @test length(r[end]) <= 4 + + # different stride + r = @inferred FixedTileRange(r0, 4, 2) + @test r == FixedTileRange(r0, 4, 2; keep_last=true) + @test r isa AbstractArray + @test eltype(r) == Vector{eltype(r0)} + @test length(r) == 4 + @test first(r) == collect(2:5) + @test last(r) == collect(8:10) + test_iteration(r) + @test all(map(x->length(x)==4, r[1:end-1])) + @test length(r[end]) <= 4 + + # discard the last tile + r = @inferred FixedTileRange(r0, 4, keep_last=false) + @test r isa AbstractArray + @test eltype(r) == Vector{eltype(r0)} + @test length(r) == 2 + @test first(r) == collect(2:5) + @test last(r) == collect(6:9) + test_iteration(r) + @test all(map(x->length(x)==4, r)) + end + end + + end # FixedTileRange + + + @testset "FixedTile" begin + ranges = [ + 2:10, + UInt8(2):UInt8(10), + Base.IdentityUnitRange(2:10), + OffsetArrays.IdOffsetRange(2:10), + OffsetArrays.IdOffsetRange(Base.OneTo(9), 1) + ] + + # scalar case + s = FixedTile(4, 2) + @test s == FixedTile(4, 2; keep_last=true) + @test s == FixedTile(4, CartesianIndex(2)) + + for r0 in ranges + s = FixedTile(4, 2) + R = @inferred s(r0) + @test s(r0) == FixedTileRange(r0, 4, 2) + + r1 = CartesianIndices((r0, )) + @test s(r1) == FixedTileRange(r1, 4, 2) + end + + # nd case + for r0 in ranges, n in 2:3 + for (sz, Δ) in [ + (4, 2), + (ntuple(_->4, n), ntuple(_->2, n)), + (fill(4, n), fill(2, n)), + (ntuple(_->4, n), ntuple(_->CartesianIndex(2), n)) + ] + s = FixedTile(sz, Δ) + indices = ntuple(_->r0, n) + R = @inferred s(indices) + @test eltype(R) <: FixedTileRange{<:UnitRange} + @test all(map(==, R, ntuple(_->FixedTileRange(r0, 4, 2), n))) + + indices = CartesianIndices(indices) + r1 = CartesianIndices((r0, )) + R = @inferred s(indices) + @test eltype(R) <: FixedTileRange{<:CartesianIndices} + @test all(map(==, R, ntuple(_->FixedTileRange(r1, 4, 2), n))) + end + end + + end # FixedTile +end