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

Fixing axes(::JuMPArray) #1623

Closed
jrevels opened this issue Nov 14, 2018 · 5 comments
Closed

Fixing axes(::JuMPArray) #1623

jrevels opened this issue Nov 14, 2018 · 5 comments
Labels
Category: Containers Related to the Containers submodule

Comments

@jrevels
Copy link
Contributor

jrevels commented Nov 14, 2018

While tackling #1455, it became apparent that the current indexing behavior of JuMPArray does not conform to the assumptions made by the AbstractArray interface. This will make it quite difficult for JuMPArray to inherit some pretty fundamental features (e.g. broadcast) and compose with other AbstractArray types/operations.

Here are some of Base's indexing assumptions:

CartesianIndices(A) === CartesianIndices(axes(A))
typeof(axes(A, d)) <: AbstractUnitRange{<:Integer}
A[axes(A)...] == A

See JuliaLang/julia#29062 and the Discourse discussion linked therein for more details.

AFAICT, there is no possible way to define axes(::JuMPArray) that a) satisfies all the above assumptions and b) doesn't change user-facing JuMPArray behavior. Right now, axes(::JuMPArray) is overloaded to respect the A[axes(A)...] == A assumption.

My attempt at resolving this without changing user-facing behavior involved defining an AxisRange <: AbstractUnitRange type wrapper, but that ended up being a no-go since Base's R<:AbstractUnitRange code assumes that an instance r::R can be indexed with some multiple of oneunit(eltype(R)) (which doesn't make sense when e.g. eltype(R) === Symbol).

Another way forward is to allow an integer indexing fallback for non-integer JuMPArray axes, e.g.

julia> @variable(m, y[2:5, [:green, :blue]])
2-dimensional JuMPArray{VariableRef,2,...} with index sets:
    Dimension 1, 2:5
    Dimension 2, Symbol[:green, :blue]
And data, a 4×2 Array{VariableRef,2}:
 y[2,green]  y[2,blue]
 y[3,green]  y[3,blue]
 y[4,green]  y[4,blue]
 y[5,green]  y[5,blue]

julia> axes(y)
(2:5, 1:2)

julia> y[3, 2]
y[3,blue]

This approach resolves the underlying issue, but has its own problems. It changes user-facing behavior in some non-trivial ways; previously disallowed indexing would now be allowed, and visa versa. I predict that the main breakage in user code would be when the user specifies an axis to be indexed by a collection with non-concrete eltype, e.g.

julia> @variable(m, x[Any[1, 3, 5], [:green, :blue]])
2-dimensional JuMPArray{VariableRef,2,...} with index sets:
    Dimension 1, Any[1, 3, 5]
    Dimension 2, Symbol[:green, :blue]
And data, a 3×2 Array{VariableRef,2}:
 x[1,green]  x[1,blue]
 x[3,green]  x[3,blue]
 x[5,green]  x[5,blue]

Under this approach, indexing into x above is ambiguous, e.g. it can't be known whether x[3, 2] should return x[3, green] or x[5, green]. It'd probably have to be made an error to construct something like x at all, documenting a user-facing rule like "Given the @variable(m, x[inds...]), all(isconcretetype(eltype(i)) for i in inds) must be true"...gross!

Anybody have any better ideas?

Honestly, it seems like the "right" way to fix this is to just refactor Base's interface, but the ship has sailed on that until Julia 2.0...

@jrevels
Copy link
Contributor Author

jrevels commented Nov 14, 2018

it can't be known whether x[3, 2] should return x[3, green] or x[5, green].

It can't be known from the type information, anyway...

We could just do a runtime lookup for every getindex, and always favor the user-specified index over the fallback, but I was assuming we wouldn't want to do that.

However, we could actually avoid doing the runtime lookup for concrete-eltyped index collections, and only do it for nonconcrete-eltyped index collections. Those getindexs would likely have involved a dynamic dispatch anyway, so maybe it wouldn't be that much slower? Hmm...

@jrevels
Copy link
Contributor Author

jrevels commented Nov 14, 2018

Okay, just thinking out loud here, but another potential solution is to define:

struct FallbackIndex <: Integer
    i::Int
end

and overload getindex(::JuMPArray) on those guys. Then we can define axes(::JuMPArray) to return a tuple of FallbackIndexRange <: AbstractUnitRange{Integer}.

This approach doesn't have the same user-facing problems of the other approach, but it might be harder to implement/more brittle, since it requires defining a new <:Integer subtype...

@mlubin
Copy link
Member

mlubin commented Nov 14, 2018

AxisArrays has an identical issue: JuliaArrays/AxisArrays.jl#84.

My initial reaction to the proposal to mix different index types like:

julia> y[3, 2]
y[3,blue]

is a strong negative. This is confusing and enables very subtle bugs in user code, and also has the weird edge cases like Any[1, 3, 5] that you brought up. Using a different type to signal integer-based indexing sounds better to me.

@blegat
Copy link
Member

blegat commented Nov 15, 2018

I like the FallbackIndex solution, this might also help implement linear indexing through getindex(::JuMPArray, ::FallbackInteger) to fix this test:
https://github.com/JuliaOpt/JuMP.jl/blob/ef17037984d9c10814f80ab5ff90689a4410b5a2/test/variable.jl#L419-L421

@odow odow added the Category: Containers Related to the Containers submodule label Dec 3, 2020
@odow
Copy link
Member

odow commented Feb 18, 2021

Closing because at some point, this got fixed.

julia> @variable(model, y[2:5, [:green, :blue]])
2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
    Dimension 1, 2:5
    Dimension 2, Symbol[:green, :blue]
And data, a 4×2 Array{VariableRef,2}:
 y[2,green]  y[2,blue]
 y[3,green]  y[3,blue]
 y[4,green]  y[4,blue]
 y[5,green]  y[5,blue]

julia> axes(y)
(2:5, Symbol[:green, :blue])

@odow odow closed this as completed Feb 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Containers Related to the Containers submodule
Development

No branches or pull requests

4 participants