Skip to content

Commit

Permalink
implement FixedTileRange, FixedTile and TileIndices
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnychen94 committed Dec 5, 2020
1 parent 42aca10 commit 52a8328
Show file tree
Hide file tree
Showing 7 changed files with 570 additions and 75 deletions.
5 changes: 3 additions & 2 deletions src/TiledIteration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 0 additions & 73 deletions src/tile.jl

This file was deleted.

83 changes: 83 additions & 0 deletions src/tileindices.jl
Original file line number Diff line number Diff line change
@@ -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
197 changes: 197 additions & 0 deletions src/tilerange.jl
Original file line number Diff line number Diff line change
@@ -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 `Δ<n` means there are overlaps between each adjacent tile. `Δ` can
also be `CartesianIndex{1}`.
- `keep_last=true` (keyword): this keyword affects the cases when the last tile has few elements
than `n`, in which case, `true`/`false` tells `FixedTileRange` to keep/discard the last tile.
# Examples
```jldoctest
julia> 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))
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,)))
Expand Down
Loading

0 comments on commit 52a8328

Please sign in to comment.