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

Fix #556, use IdentityUnitRange in place of Base.Slice #568

Closed
wants to merge 4 commits into from
Closed

Fix #556, use IdentityUnitRange in place of Base.Slice #568

wants to merge 4 commits into from

Conversation

pablosanjose
Copy link
Contributor

@pablosanjose pablosanjose commented Dec 13, 2018

This is my attempt to fix #556, using OffsetArrays/#62 and the similar code in base/abstractarrays.jl as inspiration. My understanding of the underlying Slice/IdentityUnitRange mechanisms is a bit sketchy, so please review with some care (@timholy)
With this PR, the cases in #556 (s[:, 1:1] and s[:, [true, false]]) both work and yield MArrays. In the 0.7 days, this resulted in an Array so I'm assuming we've improved over 0.7?

However s[1:2, 1:1] always yields an Array, both in 0.7, 1.1 and with this PR. This suggests to me that work is perhaps not complete here.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.02%) to 66.277% when pulling 61a4762 on pablosanjose:slices into ae14694 on JuliaArrays:master.

@pablosanjose
Copy link
Contributor Author

Okay, so I think the travis tests fail because the type of s[:, 1:2] is different in versions <=0.7 than in versions >= 1.0. How does one handle this kind of situation?

@mbauman
Copy link
Member

mbauman commented Dec 13, 2018

I just started looking into this myself from the PkgEval failures in JuliaLang/julia#30374 (comment) — this is the root cause of the AstroLib failure over there.

This seems to be a good fix. My first attempt at a fix (before finding this PR) was to peel back things at a lower level. We could inform Base that SOneTo can serve as an IdentityUnitRange itself with Base.IdentityUnitRange(s::SOneTo) = s, but that's not sufficient and it feels a bit more hacky. It might allow StaticArrays and OffsetArrays to play together a bit more nicely, but I'm not sure yet.

Regarding the fact that s[1:2, 1:1] returns an Array whereas s[:, 1:1] and s[:, [true, false]] return MArrays — I think that's a feature! I would expect, however that s[SOneTo(2), SOneTo(1)] would return an MArray. The key is that the latter constructs have statically sized indices, so they can return a statically sized output.

Edit: Oh, my last paragraph is wrong. s[:, 1:1] and s[:, [true, false]] have a static number of rows but not columns. Hmm.

@@ -126,9 +126,10 @@ similar(::Type{A}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {A<:AbstractArray
similar(::A,::Type{T}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {A<:AbstractArray,T} = similar(A, T, Size(last.(shape)))
similar(::Type{A},::Type{T}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {A<:AbstractArray,T} = similar(A, T, Size(last.(shape)))

const SOneToLike{n} = Union{SOneTo{n}, Base.Slice{SOneTo{n}}}
const SOneToLike{n} = Union{Integer, SOneTo{n}, IdentityUnitRange{SOneTo{n}}}
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 I'd include Integer here — if you want to get a static array from an indexing slice, then I think you should have to use a static array of indices to do so.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I don't include it, the method similar(::Type{A},::Type{T}, shape::Tuple{SOneToLike, Vararg{SOneToLike}}) in line 134 will not get dispatched to by the similar call from base (which is the origin of the error here), and the whole thing stops working. This seems to be the approach suggested in Base, I think. Check https://github.com/JuliaLang/julia/blob/e83693749032d28e9f5f0f75695fad46729ea2b4/base/abstractarray.jl#L581 and surrounding code/comments.

Copy link
Member

@mbauman mbauman Dec 13, 2018

Choose a reason for hiding this comment

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

Ah, right, of course. We need dispatch on a Tuple with at least one SOneTo to accomplish what we want here, which of course we cannot currently express (directly).

Here's what I think I'd do, in pseudocode:

const PossiblyStaticAxis = Union{Integer, OneTo, SOneTo, IdentityUnitRange{<:SOneTo}}
similar(, ::Tuple{PossiblyStaticAxis, Vararg{PossiblyStaticAxis}}) = static_similar()

static_similar(, ::Tuple{SOneTo, Vararg{SOneTo}}) = actually_static_similar()
static_similar(, ::Tuple{Any, Vararg{Any}}) = similar(, map(length, ))

I think that should do the trick — and hopefully make things work much better all around. The method tables of similar are disastrously messy, so this is indeed quite hard.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I confess I struggle a bit getting my head around this :-). Thanks for your suggestion Matt. Feel free to take over this PR if you want. Otherwise I'll implement your suggestion tomorrow and report back.

Copy link
Contributor Author

@pablosanjose pablosanjose Dec 14, 2018

Choose a reason for hiding this comment

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

The problem with the modification you propose, as far as I can see, is that it only dispatches to actually_static_similar those shapes that are all SOneTo. The original PR first converts each PossiblyStaticAxis element of shape into an integer (the size along each direction), regardless of whether it is an SOneTo, and IdentityUnitRange or an actual Int. It then transforms it into a Size and proceeds.
Another little problem is that I think OneTo needs not be a subtype of PossiblyStaticAxis, as it gets converted to and Int in base, before getting here. After playing with it for a while I've sort of convinced myself that the current PR is probably ok, without considerably reworking the design.

One of the problems of the current design, it seems to me, is the difficulty of making it yield and MArray when shape is an NTuple{Int}, which is why s[1:2, 1:2] isa Array instead of returning an MArray as I think it should. If I try to add a specific dispatch for this, like

similar(::SA, ::Type{T}, shape::Tuple{Integer, Vararg{Integer}}) where {SA<:StaticArray,T} = similar(SA, T, Size(shape))

I run into an ambiguity with the default similar method fallback in base similar(a::AbstractArray, ::Type{T}, dims::Dims{N}).

EDIT: Scratch that, it's as easy as doing

similar(::SA, ::Type{T}, shape::Base.Dims) where {SA<:StaticArray,T} = similar(SA, T, Size(shape))

I added this to the current PR, which now has this nice added bonus

julia> s = @SArray[1 2; 3 4]
2×2 SArray{Tuple{2,2},Int64,2,4}:
 1  2
 3  4

julia> s[1:2, 1:1]
2×1 MArray{Tuple{2,1},Int64,2,2}:
 1
 3

mbauman added a commit to mbauman/StaticArrays.jl that referenced this pull request Dec 14, 2018
This restructures the similar method table along the lines that I suggested in JuliaArrays#568 (comment). I also used a bit of internals magic to match how the Base `OneTo` operates -- this is a little fragile but I think it is a good way to replicate how `OneTo` works.
@pablosanjose
Copy link
Contributor Author

@mbauman : Oops, didn't realise you had began another PR on this and I pushed another commit. No problem, go ahead and close this when you are satisfied. Do have a look at my reply above, particularly to the possibility of returning an MArray for Base.Dims slices

@pablosanjose
Copy link
Contributor Author

Closing in favor of #569

c42f pushed a commit to mbauman/StaticArrays.jl that referenced this pull request Dec 21, 2018
This restructures the similar method table along the lines that I suggested in JuliaArrays#568 (comment). I also used a bit of internals magic to match how the Base `OneTo` operates -- this is a little fragile but I think it is a good way to replicate how `OneTo` works.
c42f pushed a commit that referenced this pull request Dec 21, 2018
This restructures the similar method table along the lines that I suggested in #568 (comment). I also used a bit of internals magic to match how the Base `OneTo` operates -- this is a little fragile but I think it is a good way to replicate how `OneTo` works.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Slicing with colon and range fails
3 participants