Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: rework TileIterator #27

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/TiledIteration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ else
_inc(state, iter) = inc(state, iter.indices)
end

export TileIterator, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile
export TileIterator, FixedTileRange, FixedTile, TileIndices, EdgeIterator, padded_tilesize, TileBuffer, RelaxStride, RelaxLastTile

include("tileiterator.jl")
include("tilerange.jl")
include("tileindices.jl")

const L1cachesize = 2^15
const cachelinesize = 64
Expand Down
86 changes: 86 additions & 0 deletions src/tileindices.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
### 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 `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
`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; setup=:(using TiledIteration)
julia> TileIndices((1:4, 0:5), (3, 4), (2, 3))
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{$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; setup=:(using TiledIteration)
julia> TileIndices((1:4, 0:5), 3, 2)
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{$Int},UnitRange{$Int}},2,FixedTileRange{UnitRange{$Int},$Int,UnitRange{$Int}}}:
(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 `indices` using provided tile strategy `s`.

`indices` are tuple of ranges, or `CartesianIndices`.

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; setup=:(using TiledIteration)
julia> FixedTileRange(2:10, 3)
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,UnitRange{Int64}}:
1:4
5:8
9:10

julia> FixedTileRange(1:10, 4, 2)
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,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; setup=:(using TiledIteration)
julia> FixedTileRange(CartesianIndices((1:10, )), 4)
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,)]
```
Comment on lines +82 to +88
Copy link
Member Author

@johnnychen94 johnnychen94 Dec 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's even more ugly and unstable when it comes to N-d case.

julia> TileIndices(CartesianIndices((1:20, 1:20)), (7, 7))
3×3 TileIndices{Tuple{CartesianIndices{1, Tuple{UnitRange{Int64}}}, CartesianIndices{1, Tuple{UnitRange{Int64}}}}, 2, FixedTileRange{CartesianIndices{1, Tuple{UnitRange{Int64}}}, Int64, CartesianIndices{1, Tuple{UnitRange{Int64}}}}}:
 CartesianIndex{2}[CartesianIndex(1, 1) CartesianIndex(1, 2)  CartesianIndex(1, 6) CartesianIndex(1, 7); CartesianIndex(2, 1) CartesianIndex(2, 2)  CartesianIndex(2, 6) CartesianIndex(2, 7);  ; CartesianIndex(6, 1) CartesianIndex(6, 2)  CartesianIndex(6, 6) CartesianIndex(6, 7); CartesianIndex(7, 1) CartesianIndex(7, 2)  CartesianIndex(7, 6) CartesianIndex(7, 7)]                    CartesianIndex{2}[CartesianIndex(1, 15) CartesianIndex(1, 16)  CartesianIndex(1, 19) CartesianIndex(1, 20); CartesianIndex(2, 15) CartesianIndex(2, 16)  CartesianIndex(2, 19) CartesianIndex(2, 20);  ; CartesianIndex(6, 15) CartesianIndex(6, 16)  CartesianIndex(6, 19) CartesianIndex(6, 20); CartesianIndex(7, 15) CartesianIndex(7, 16)  CartesianIndex(7, 19) CartesianIndex(7, 20)]
 CartesianIndex{2}[CartesianIndex(8, 1) CartesianIndex(8, 2)  CartesianIndex(8, 6) CartesianIndex(8, 7); CartesianIndex(9, 1) CartesianIndex(9, 2)  CartesianIndex(9, 6) CartesianIndex(9, 7);  ; CartesianIndex(13, 1) CartesianIndex(13, 2)  CartesianIndex(13, 6) CartesianIndex(13, 7); CartesianIndex(14, 1) CartesianIndex(14, 2)  CartesianIndex(14, 6) CartesianIndex(14, 7)]             CartesianIndex{2}[CartesianIndex(8, 15) CartesianIndex(8, 16)  CartesianIndex(8, 19) CartesianIndex(8, 20); CartesianIndex(9, 15) CartesianIndex(9, 16)  CartesianIndex(9, 19) CartesianIndex(9, 20);  ; CartesianIndex(13, 15) CartesianIndex(13, 16)  CartesianIndex(13, 19) CartesianIndex(13, 20); CartesianIndex(14, 15) CartesianIndex(14, 16)  CartesianIndex(14, 19) CartesianIndex(14, 20)]
 CartesianIndex{2}[CartesianIndex(15, 1) CartesianIndex(15, 2)  CartesianIndex(15, 6) CartesianIndex(15, 7); CartesianIndex(16, 1) CartesianIndex(16, 2)  CartesianIndex(16, 6) CartesianIndex(16, 7);  ; CartesianIndex(19, 1) CartesianIndex(19, 2)  CartesianIndex(19, 6) CartesianIndex(19, 7); CartesianIndex(20, 1) CartesianIndex(20, 2)  CartesianIndex(20, 6) CartesianIndex(20, 7)]     CartesianIndex{2}[CartesianIndex(15, 15) CartesianIndex(15, 16)  CartesianIndex(15, 19) CartesianIndex(15, 20); CartesianIndex(16, 15) CartesianIndex(16, 16)  CartesianIndex(16, 19) CartesianIndex(16, 20);  ; CartesianIndex(19, 15) CartesianIndex(19, 16)  CartesianIndex(19, 19) CartesianIndex(19, 20); CartesianIndex(20, 15) CartesianIndex(20, 16)  CartesianIndex(20, 19) CartesianIndex(20, 20)]

and it simply crashes during the display if I use CartesianIndices((1:512, 1:512))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to display those CartesianIndices as

[CartesianIndex(1, 1):CartesianIndex(1, 7), CartesianIndex(1, 1):CartesianIndex(8, 15), CartesianIndex(1, 1):CartesianIndex(16, 20)]

while still keep the current show behavior?

I vaguely remember that we have a top_level keyword for display related function, but can't find the name of it now.


!!! 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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters could use some clarificiation, maybe just by subtyping, e.g., R<:AbstractVector{<:Integer} or something?

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that this is conceptually equivalent to StepRange(1:5, BroadcastInt(2), 7:11) (a range-of-ranges). Should we consider the FixedTileRange in terms of the range API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, not sure if I understand it right, do you mean StepRange(1:5, BroadcastInt(2), 7:11) is equivalent to

julia> TileIndices((1:5, 7:11), 2)
3×3 TileIndices{Tuple{UnitRange{Int64},UnitRange{Int64}},2,FixedTileRange{UnitRange{Int64},Int64,UnitRange{Int64}}}:
 (1:2, 7:8)  (1:2, 9:10)  (1:2, 11:11)
 (3:4, 7:8)  (3:4, 9:10)  (3:4, 11:11)
 (5:5, 7:8)  (5:5, 9:10)  (5:5, 11:11)

? I couldn't find what BroadcastInt is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider the FixedTileRange in terms of the range API?

I would be largely relieved if we could separate the indices and tile indices world, just like how we are doing with normal array and fancy block arrays. If that's what you were thinking about.

or are you saying to could make something like FixedTileRange(start, stop; length, Δ, n) only to mimic range's usage? That would be a little bit hard because I do plan to support all indices types, including the vectors and CartesianIndices. Mimicking the range API won't improve the useability much IMO.

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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're standardizing OrdinalRange and AbstractUnitRange but allowing any other type of AbstractVector? Does Vector{Int} count?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Vector{Int} counts even though it is not memory-efficient. I choose to support that because Vector{Int} is a valid indices type.

Supporting this enables us to pass lazy evaluated vectors, even though I'm not sure if there's practical usage right now. Currently, I'm mostly concerned about CartesianIndices and step range.


_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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to define this (the fallbacks do it).


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; 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,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,UnitRange{Int64}}}:
(1:3, 0:3)
```

When `sz` and `Δ` are scalars, it affects each dimension equivalently.

```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,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,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
87 changes: 87 additions & 0 deletions test/tileindices.jl
Original file line number Diff line number Diff line change
@@ -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
Loading