From a2d3b5b10a7855f0acb6e4b4e36036256a783718 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Wed, 2 Dec 2020 06:09:24 +0800 Subject: [PATCH 1/7] WIP: rework TileIterator --- src/TiledIteration.jl | 3 ++- src/tile.jl | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/tile.jl diff --git a/src/TiledIteration.jl b/src/TiledIteration.jl index 6161ae3..423e76b 100644 --- a/src/TiledIteration.jl +++ b/src/TiledIteration.jl @@ -11,9 +11,10 @@ else _inc(state, iter) = inc(state, iter.indices) end -export TileIterator, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile +export TileIterator, TiledUnitRange, TiledIndices, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile include("tileiterator.jl") +include("tile.jl") const L1cachesize = 2^15 const cachelinesize = 64 diff --git a/src/tile.jl b/src/tile.jl new file mode 100644 index 0000000..f66b5eb --- /dev/null +++ b/src/tile.jl @@ -0,0 +1,46 @@ +### 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<:AbstractUnitRange{T}} + n = ceil(T, (last(parent) - tilelength)/tilestride) + 1 + 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) + +Base.length(r::TiledUnitRange) = r.length + +function Base.first(r::TiledUnitRange{T, R}) where {T, R} + start = first(r.parent) + return R(start, min(start+r.length, last(r.parent))) +end + +function Base.last(r::TiledUnitRange{T, R}) where {T, R} + start = first(r.parent) + (length(r)-1) * r.tilestride + return R(start, min(start+r.length, last(r.parent))) +end + +function Base.getindex(r::TiledUnitRange{T, R}, i::Int) where {T, R} + start = first(r.parent) + (i-1)*r.tilestride + return R(start, min(start+r.length, last(r.parent))) +end + +Base.show(io::IO, r::TiledUnitRange) = print(io, "TiledUnitRange(", r.parent, ",", r.tilelength, ",", r.tilestride, ")") + +### 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)) + +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) From 42aca10dd0b47a47132d38ff21953c03ad2240d5 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Wed, 2 Dec 2020 07:18:16 +0800 Subject: [PATCH 2/7] add CartesianIndices support --- src/tile.jl | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/tile.jl b/src/tile.jl index f66b5eb..b902710 100644 --- a/src/tile.jl +++ b/src/tile.jl @@ -6,32 +6,47 @@ struct TiledUnitRange{T, R} <: AbstractUnitRange{T} tilestride::T length::T - function TiledUnitRange{T, R}(parent::R, tilelength::T, tilestride::T) where {T, R<:AbstractUnitRange{T}} - n = ceil(T, (last(parent) - tilelength)/tilestride) + 1 + 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) - return R(start, min(start+r.length, last(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) * r.tilestride - return R(start, min(start+r.length, last(r.parent))) + 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)*r.tilestride - return R(start, min(start+r.length, last(r.parent))) + 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 @@ -40,7 +55,19 @@ struct TiledIndices{N, T, R} <: AbstractArray{R, N} end TiledIndices(indices, tilelength) = TiledIndices(indices, tilelength, tilelength) -TiledIndices(indices, tilelength, tilestride) = TiledIndices(map(TiledUnitRange, indices, tilelength, tilestride)) +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 + + From 52a8328c8586ed6f61d56a9deb5596e52a87489f Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Sun, 6 Dec 2020 06:26:42 +0800 Subject: [PATCH 3/7] implement FixedTileRange, FixedTile and TileIndices --- src/TiledIteration.jl | 5 +- src/tile.jl | 73 ---------------- src/tileindices.jl | 83 ++++++++++++++++++ src/tilerange.jl | 197 +++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 2 + test/tileindices.jl | 87 +++++++++++++++++++ test/tilerange.jl | 198 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 570 insertions(+), 75 deletions(-) delete mode 100644 src/tile.jl create mode 100644 src/tileindices.jl create mode 100644 src/tilerange.jl create mode 100644 test/tileindices.jl create mode 100644 test/tilerange.jl 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 From 9946e3183b62d7521cc1a5f8a19ebc2888106cb5 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Sun, 6 Dec 2020 06:54:15 +0800 Subject: [PATCH 4/7] fix doctest --- src/tileindices.jl | 23 ++++++++++++---------- src/tilerange.jl | 48 +++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/tileindices.jl b/src/tileindices.jl index 3571643..4b307d9 100644 --- a/src/tileindices.jl +++ b/src/tileindices.jl @@ -10,10 +10,11 @@ end """ TileIndices(indices, sz, Δ=sz; keep_last=true) -Construct a sliding tile along axes `r` with fixed sliding strides `Δ` and tile size `sz`. +Construct a sliding tile along axes `indices` with fixed sliding strides `Δ` and tile size `sz`. # Arguments +- `indices`: tuple of ranges, or `CartesianIndices`. - `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 @@ -24,27 +25,27 @@ Construct a sliding tile along axes `r` with fixed sliding strides `Δ` and tile # Examples -```jldoctest +```jldoctest; setup=:(using TiledIteration) 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) +2×2 TileIndices{Tuple{UnitRange{$Int},UnitRange{$Int}},2,FixedTileRange{UnitRange{$Int},$Int,UnitRange{$Int}}}: + (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×1 TileIndices{Tuple{UnitRange{$Int},UnitRange{$Int}},2,FixedTileRange{UnitRange{$Int},$Int,UnitRange{$Int}}}: (1:3, 0:3) ``` When `sz` and `Δ` are scalars, it affects each dimension equivalently. -```jldoctest +```jldoctest; setup=:(using TiledIteration) 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}}}: +2×3 TileIndices{Tuple{UnitRange{$Int},UnitRange{$Int}},2,FixedTileRange{UnitRange{$Int},$Int,UnitRange{$Int}}}: (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×2 TileIndices{Tuple{UnitRange{$Int},UnitRange{$Int}},2,FixedTileRange{UnitRange{$Int},$Int,UnitRange{$Int}}}: (1:3, 0:2) (1:3, 2:4) ``` @@ -57,7 +58,9 @@ TileIndices(indices, n, Δ=n; kwargs...) = TileIndices(indices, FixedTile(n, Δ; """ TileIndices(indices, s::AbstractTileStrategy) -Construct a sliding tile along axes `r` using provided tile strategy `s`. +Construct a sliding tile along axes `indices` using provided tile strategy `s`. + +`indices` are tuple of ranges, or `CartesianIndices`. Currently available strategies are: diff --git a/src/tilerange.jl b/src/tilerange.jl index 8055ab1..73d1ccd 100644 --- a/src/tilerange.jl +++ b/src/tilerange.jl @@ -44,41 +44,41 @@ Construct a sliding tile along range `r` with fixed sliding stride `Δ` and tile # Examples -```jldoctest +```jldoctest; setup=:(using TiledIteration) julia> FixedTileRange(2:10, 3) -3-element FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}: +3-element FixedTileRange{UnitRange{Int64},Int64,UnitRange{Int64}}: 2:4 5:7 8:10 julia> FixedTileRange(1:10, 4) -3-element FixedTileRange{UnitRange{Int64}, Int64, Val{true}, UnitRange{Int64}}: +3-element FixedTileRange{UnitRange{Int64},Int64,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 +4-element FixedTileRange{UnitRange{Int64},Int64,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 +2-element FixedTileRange{UnitRange{Int64},Int64,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 +```jldoctest; setup=:(using TiledIteration) 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,)] +3-element FixedTileRange{CartesianIndices{1,Tuple{UnitRange{Int64}}},Int64,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, @@ -150,27 +150,27 @@ A tile strategy that you can use to construct [`TileIndices`](@ref) using [`Fixe # Examples -```jldoctest +```jldoctest; setup=:(using TiledIteration) 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) +2×2 TileIndices{Tuple{UnitRange{Int64},UnitRange{Int64}},2,FixedTileRange{UnitRange{Int64},Int64,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×1 TileIndices{Tuple{UnitRange{Int64},UnitRange{Int64}},2,FixedTileRange{UnitRange{Int64},Int64,UnitRange{Int64}}}: (1:3, 0:3) ``` When `sz` and `Δ` are scalars, it affects each dimension equivalently. -```jldoctest +```jldoctest; setup=:(using TiledIteration) 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}}}: +2×3 TileIndices{Tuple{UnitRange{Int64},UnitRange{Int64}},2,FixedTileRange{UnitRange{Int64},Int64,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×2 TileIndices{Tuple{UnitRange{Int64},UnitRange{Int64}},2,FixedTileRange{UnitRange{Int64},Int64,UnitRange{Int64}}}: (1:3, 0:2) (1:3, 2:4) ``` """ From 855918db3f4176586cd30acf0dd2980d71828f16 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Sun, 6 Dec 2020 20:19:24 +0800 Subject: [PATCH 5/7] compat fix for Julia 1.0 * oneunit for CartesianIndex * (:) method for CartesianIndex * IdentityUnitRange --- Project.toml | 2 +- src/TiledIteration.jl | 1 + src/compat.jl | 8 ++++++++ src/tilerange.jl | 2 +- test/compat.jl | 5 +++++ test/runtests.jl | 3 ++- test/tilerange.jl | 24 +++++++++--------------- 7 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 src/compat.jl create mode 100644 test/compat.jl diff --git a/Project.toml b/Project.toml index 0f60571..5ef8a72 100644 --- a/Project.toml +++ b/Project.toml @@ -11,8 +11,8 @@ OffsetArrays = "0.8, 0.9, 0.10, 0.11, 1" julia = "1" [extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Test", "Documenter"] diff --git a/src/TiledIteration.jl b/src/TiledIteration.jl index 60cd3d6..c6ab805 100644 --- a/src/TiledIteration.jl +++ b/src/TiledIteration.jl @@ -13,6 +13,7 @@ end export TileIterator, FixedTileRange, FixedTile, TileIndices, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile +include("compat.jl") include("tileiterator.jl") include("tilerange.jl") include("tileindices.jl") diff --git a/src/compat.jl b/src/compat.jl new file mode 100644 index 0000000..a138ae6 --- /dev/null +++ b/src/compat.jl @@ -0,0 +1,8 @@ +# https://github.com/JuliaLang/julia/pull/29442 +if VERSION <= v"1.0.5" + _oneunit(x) = Base.oneunit(x) + _oneunit(::CartesianIndex{N}) where {N} = _oneunit(CartesianIndex{N}) + _oneunit(::Type{CartesianIndex{N}}) where {N} = CartesianIndex(ntuple(x -> 1, Val(N))) +else + _oneunit = Base.oneunit +end diff --git a/src/tilerange.jl b/src/tilerange.jl index 73d1ccd..706138b 100644 --- a/src/tilerange.jl +++ b/src/tilerange.jl @@ -128,7 +128,7 @@ 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)) + stop = min(start+tilelength(r)-_oneunit(eltype(R)), last(r.parent)) return convert(R, start:stop) end diff --git a/test/compat.jl b/test/compat.jl new file mode 100644 index 0000000..e3587c1 --- /dev/null +++ b/test/compat.jl @@ -0,0 +1,5 @@ +# https://github.com/JuliaLang/julia/pull/29440 +if VERSION < v"1.1.0-DEV.389" + Base.:(:)(I::CartesianIndex{N}, J::CartesianIndex{N}) where N = + CartesianIndices(map((i,j) -> i:j, Tuple(I), Tuple(J))) +end diff --git a/test/runtests.jl b/test/runtests.jl index dcf2e79..a406740 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,7 @@ using Test using Documenter using OffsetArrays: IdentityUnitRange -if VERSION < v"1.6-" +if v"1.1" <= VERSION < v"1.6-" Documenter.doctest(TiledIteration) # Version restriction can be lifted, when # filters can be passed to `doctest` @@ -17,6 +17,7 @@ if VERSION < v"1.6-" # Documenter.doctest(TiledIteration, doctestfilters = doctestfilters) end +include("compat.jl") include("tilerange.jl") include("tileindices.jl") diff --git a/test/tilerange.jl b/test/tilerange.jl index 068eefe..b3760a7 100644 --- a/test/tilerange.jl +++ b/test/tilerange.jl @@ -1,4 +1,12 @@ @testset "Fixed Tile" begin + ranges = [ + 2:10, + UInt8(2):UInt8(10), + OffsetArrays.IdentityUnitRange(2:10), # Julia 1.0 compat: Base.IdentityUnitRange was Base.Slice + OffsetArrays.IdOffsetRange(2:10), + OffsetArrays.IdOffsetRange(Base.OneTo(9), 1) + ] + @testset "FixedTileRange" begin function test_iteration(r) for (i, v) in enumerate(r) @@ -14,13 +22,7 @@ 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) - ] + for r0 in ranges # keep the last tile r = @inferred FixedTileRange(r0, 4) @test r == FixedTileRange(r0, 4; keep_last=true) == FixedTileRange(r0, 4, 4) @@ -150,14 +152,6 @@ @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) From 519a9844e68c0f391e77124dbc516c0a680169bf Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Sun, 6 Dec 2020 20:20:03 +0800 Subject: [PATCH 6/7] ignore Manifest.toml --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8c960ec..3f02ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.jl.cov *.jl.*.cov *.jl.mem +Manifest.toml From 3c5977d8f9acd54a240f9e4e2b8987c888e4dbd3 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Mon, 7 Dec 2020 00:06:01 +0800 Subject: [PATCH 7/7] add StepRange support --- src/tilerange.jl | 67 +++++++++++++------- test/tileindices.jl | 77 +++++++++++++++++++++++ test/tilerange.jl | 147 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 253 insertions(+), 38 deletions(-) diff --git a/src/tilerange.jl b/src/tilerange.jl index 706138b..1e742d0 100644 --- a/src/tilerange.jl +++ b/src/tilerange.jl @@ -16,13 +16,14 @@ # | range/vector | AbstractTileRange | # | CartesianIndices | TileIndices | # -# AbstractTileStrategy serves as an adapter for `AbstractTileRange` and `TileIndices` +# AbstractTileStrategy serves as an adapter for `AbstractTileRange` and `TileIndices` to give a more +# flexible API specification. abstract type AbstractTileRange{R} <: AbstractArray{R, 1} end abstract type AbstractTileStrategy end -const Range1 = Union{AbstractUnitRange, AbstractVector} +const Range1 = Union{OrdinalRange{<:Integer}, AbstractVector{<:Integer}, CartesianIndices{1}} ### FixedTileRange and FixedTile @@ -68,6 +69,11 @@ julia> FixedTileRange(1:10, 4; keep_last=false) 2-element FixedTileRange{UnitRange{Int64},Int64,UnitRange{Int64}}: 1:4 5:8 + +julia> FixedTileRange(1:2:10, 4) +2-element FixedTileRange{StepRange{Int64,Int64},Int64,StepRange{Int64,Int64}}: + 1:2:7 + 5:2:9 ``` Besides an `AbstractUnitRange`, the input range `r` can also be a `CartesianIndices{1}` or more @@ -81,7 +87,8 @@ julia> FixedTileRange(CartesianIndices((1:10, )), 4) [CartesianIndex(9,), CartesianIndex(10,)] ``` -!!! warning It usually has bad indexing performance if `r` is not lazily evaluated. For example, +!!! 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. """ @@ -95,41 +102,54 @@ struct FixedTileRange{R, T, RP} <: AbstractTileRange{R} 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) + _length = _fixedtile_length(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<:OrdinalRange = StepRange{eltype(R), eltype(R)} _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 +function _fixedtile_length(r::OrdinalRange{T}, n, Δ, keep_last) where T<:Integer _round = keep_last ? ceil : floor - return _round(T, (stop - n - start + 1)/_int(Δ)) + 1 + start, step, stop = first(r), Base.step(r), last(r) + return _round(T, (stop - step*n - start + 1)/_int(Δ)) + 1 +end +function _fixedtile_length(r::CartesianIndices{1}, n, Δ, keep_last) + _fixedtile_length(r.indices[1], n, Δ, keep_last) +end +function _fixedtile_length(r::AbstractVector{T}, n, Δ, keep_last) where T<:Integer + _fixedtile_length(UnitRange{T}(first(r), last(r)), n, Δ, keep_last) 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} + convert(R, _getindex(r.parent, r.n, r.Δ, i)) +end +Base.@propagate_inbounds function Base.getindex(r::FixedTileRange{R, T}, i::Int) where {R<:CartesianIndices, T} + # inter-operation between CartesianIndex and Integer is not very well defined + # for this reason, we deconstruct CartesianIndex into range, index, and reconstruct it. + R((_getindex(r.parent.indices[1], r.n, r.Δ, i), )) +end + +Base.@propagate_inbounds function _getindex(r::R, n::T, Δ::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) + start = first(r) + (i-1)*Δ + stop = start + n - _oneunit(eltype(R)) + return start:min(stop, last(r)) +end +Base.@propagate_inbounds function _getindex(r::R, n::T, Δ::T, i::Int) where {R<:StepRange, T} + @boundscheck checkbounds(r, i) + start = first(r) + (i-1)*Δ + stop = start + step(r)*n - _oneunit(eltype(R)) + return start:step(r):min(stop, last(r)) end @@ -159,6 +179,12 @@ julia> TileIndices((1:4, 0:5), FixedTile((3, 4), (2, 3))) 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,UnitRange{Int64}}}: (1:3, 0:3) + +julia> TileIndices((1:2:10, 0:1:5), FixedTile((3, 4), (2, 3))) +3×2 TileIndices{Tuple{StepRange{Int64,Int64},StepRange{Int64,Int64}},2,FixedTileRange{StepRange{Int64,Int64},Int64,StepRange{Int64,Int64}}}: + (1:2:5, 0:1:3) (1:2:5, 3:1:5) + (3:2:7, 0:1:3) (3:2:7, 3:1:5) + (5:2:9, 0:1:3) (5:2:9, 3:1:5) ``` When `sz` and `Δ` are scalars, it affects each dimension equivalently. @@ -185,8 +211,7 @@ FixedTile(sz::T, Δ=sz; keep_last=true) where T = FixedTile{length(sz), T}(sz, (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{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.Δ) diff --git a/test/tileindices.jl b/test/tileindices.jl index 19c6ed5..104a598 100644 --- a/test/tileindices.jl +++ b/test/tileindices.jl @@ -43,6 +43,45 @@ @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) + + # StepRange + R0 = (1:2:8, 0:3:20) + + 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{StepRange{Int, Int}, StepRange{Int, Int}} + @test size(R) == (2, 4) + @test R[1] == (1:2:5, 0:3:9) + @test R[end] == (3:2:7, 9:3:18) + 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{StepRange{Int, Int}, StepRange{Int, Int}} + @test size(R) == (1, 3) + @test R[1] == (1:2:5, 0:3:9) + @test R[end] == (1:2:5, 6:3:15) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile(3, 2)) + @test R == TileIndices(R0, 3, 2) + @test R isa AbstractArray + @test eltype(R) == Tuple{StepRange{Int, Int}, StepRange{Int, Int}} + @test size(R) == (2, 6) + @test R[1] == R[1, 1] == (1:2:5, 0:3:6) + @test R[3] == R[1, 2] == (1:2:5, 2:3:8) + @test R[end] == R[12] == (3:2:7, 10:3:16) + 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{StepRange{Int, Int}, StepRange{Int, Int}} + @test size(R) == (1, 6) + @test R[1] == R[1, 1] == (1:2:5, 0:3:6) + @test R[6] == R[1, 6] == R[end] == (1:2:5, 10:3:16) end @testset "CartesianIndices" begin @@ -82,6 +121,44 @@ @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)) + + if VERSION >= v"1.6.0-DEV.1174" + # StepRange CartesianIndices + # https://github.com/JuliaLang/julia/pull/37829 + R0 = CartesianIndices((1:2:8, 0:3:20)) + + R = @inferred TileIndices(R0, FixedTile((3, 4), (2, 3))) + @test R == TileIndices(R0, (3, 4), (2, 3)) + @test R isa AbstractArray + @test size(R) == (2, 4) + @test R[1] == CartesianIndices((1:2:5, 0:3:9)) + @test R[end] == CartesianIndices((3:2:7, 9:3:18)) + 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 size(R) == (1, 3) + @test R[1] == CartesianIndices((1:2:5, 0:3:9)) + @test R[end] == CartesianIndices((1:2:5, 6:3:15)) + test_iteration(R) + + R = @inferred TileIndices(R0, FixedTile(3, 2)) + @test R == TileIndices(R0, 3, 2) + @test R isa AbstractArray + @test size(R) == (2, 6) + @test R[1] == R[1, 1] == CartesianIndices((1:2:5, 0:3:6)) + @test R[3] == R[1, 2] == CartesianIndices((1:2:5, 2:3:8)) + @test R[end] == R[12] == CartesianIndices((3:2:7, 10:3:16)) + 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 size(R) == (1, 6) + @test R[1] == R[1, 1] == CartesianIndices((1:2:5, 0:3:6)) + @test R[6] == R[1, 6] == R[end] == CartesianIndices((1:2:5, 10:3:16)) + end end end end diff --git a/test/tilerange.jl b/test/tilerange.jl index b3760a7..ce3a207 100644 --- a/test/tilerange.jl +++ b/test/tilerange.jl @@ -58,6 +58,44 @@ @test all(map(x->length(x)==4, r)) end + @testset "StepRange" begin + r0 = 2:3:20 + + # 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) == StepRange{eltype(r0), eltype(r0)} + @test length(r) == 3 + @test first(r) == 2:3:11 + @test last(r) == 10:3:19 + 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) == StepRange{eltype(r0), eltype(r0)} + @test length(r) == 5 + @test first(r) == 2:3:11 + @test last(r) == 10:3:19 + 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) == StepRange{eltype(r0), eltype(r0)} + @test length(r) == 2 + @test first(r) == 2:3:11 + @test last(r) == 6:3:15 + 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 @@ -101,6 +139,48 @@ @test last(r) == CartesianIndex(6):CartesianIndex(9) # test_iteration(r) @test all(map(x->length(x)==4, r)) + + @testset "StepRange" begin + if VERSION >= v"1.6.0-DEV.1174" + # StepRange CartesianIndices + # https://github.com/JuliaLang/julia/pull/37829 + r0 = CartesianIndex(2):CartesianIndex(3):CartesianIndex(20) + + # 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) == CartesianIndices{1, Tuple{StepRange{Int64, Int64}}} + @test length(r) == 3 + @test first(r) == CartesianIndex(2):CartesianIndex(3):CartesianIndex(11) + @test last(r) == CartesianIndex(10):CartesianIndex(3):CartesianIndex(19) + 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) == CartesianIndices{1, Tuple{StepRange{Int64, Int64}}} + @test length(r) == 5 + @test first(r) == CartesianIndex(2):CartesianIndex(3):CartesianIndex(11) + @test last(r) == CartesianIndex(10):CartesianIndex(3):CartesianIndex(19) + 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) == CartesianIndices{1, Tuple{StepRange{Int64, Int64}}} + @test length(r) == 2 + @test first(r) == CartesianIndex(2):CartesianIndex(3):CartesianIndex(11) + @test last(r) == CartesianIndex(6):CartesianIndex(3):CartesianIndex(15) + test_iteration(r) + @test all(map(x->length(x)==4, r)) + end + end end @testset "AbstractVector" begin @@ -150,43 +230,76 @@ end # FixedTileRange - @testset "FixedTile" begin # scalar case - s = FixedTile(4, 2) - @test s == FixedTile(4, 2; keep_last=true) - @test s == FixedTile(4, CartesianIndex(2)) + @testset "scalar case" begin + 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 R == FixedTileRange(r0, 4, 2) + + r1 = CartesianIndices((r0, )) + @test s(r1) == FixedTileRange(r1, 4, 2) + end - for r0 in ranges + # StepRange + r0 = 2:3:20 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) + @test R == FixedTileRange(r0, 4, 2) + if VERSION >= v"1.6.0-DEV.1174" + # StepRange CartesianIndices + # https://github.com/JuliaLang/julia/pull/37829 + r1 = CartesianIndices((r0, )) + R = @inferred s(r1); + @test R == FixedTileRange(r1, 4, 2) + end end # nd case - for r0 in ranges, n in 2:3 + for 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)) ] + for r0 in ranges + 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 + + # StepRange + r0 = 2:3:20 s = FixedTile(sz, Δ) indices = ntuple(_->r0, n) R = @inferred s(indices) - @test eltype(R) <: FixedTileRange{<:UnitRange} + @test eltype(R) <: FixedTileRange{<:StepRange} @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))) + if VERSION >= v"1.6.0-DEV.1174" + # StepRange CartesianIndices + # https://github.com/JuliaLang/julia/pull/37829 + 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 - end # FixedTile end