Skip to content

Commit 1438546

Browse files
committed
Container revamp (#1099)
Replace JuMPDict with Dict. Rewrite JuMPArray to be compatible with AbstractArray. Explicit keyword argument in macro to force container type. Closes #1099 Closes #1047 Closes #417 (collect is now well defined for Array, JuMPArray, and Dict) Closes #833 (`eachindex` and `indices` are defined for JuMPArray) Closes #740 (dot broadcast syntax is now the default, no need to explicitly define vectorized functions) Closes #922 (fixed by checking for duplicates) Closes #933 (corollary: closes #346) Closes #643 (colons work for Array and JuMPArray, obviously not Dict) Closes #730 (end is not supported for JuMPArray) Closes #646 (we now rely on built-in iteration behavior for Dict)
1 parent 7ec1066 commit 1438546

10 files changed

+597
-715
lines changed

src/JuMP.jl

+2-25
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export
6363
@objective, @NLobjective,
6464
@NLparameter, @constraintref
6565

66-
include("JuMPContainer.jl")
66+
6767
include("utils.jl")
6868

6969
const MOIVAR = MOI.VariableReference
@@ -164,7 +164,6 @@ mutable struct Model <: AbstractModel
164164

165165
objdict::Dict{Symbol,Any} # dictionary from variable and constraint names to objects
166166

167-
map_counter::Int # number of times we call getvalue, getdual, getlowerbound and getupperbound on a JuMPContainer, so that we can print out a warning
168167
operator_counter::Int # number of times we add large expressions
169168

170169
# Extension dictionary - e.g. for robust
@@ -201,7 +200,6 @@ mutable struct Model <: AbstractModel
201200
m.nlpdata = nothing
202201
m.simplify_nonlinear_expressions = simplify_nonlinear_expressions
203202
m.objdict = Dict{Symbol,Any}()
204-
m.map_counter = 0
205203
m.operator_counter = 0
206204
m.ext = Dict{Symbol,Any}()
207205

@@ -535,14 +533,6 @@ Base.copy(v::Variable, new_model::Model) = Variable(new_model, v.col)
535533
Base.copy(x::Void, new_model::Model) = nothing
536534
Base.copy(v::AbstractArray{Variable}, new_model::Model) = (var -> Variable(new_model, var.col)).(v)
537535

538-
# Copy methods for variable containers
539-
Base.copy(d::JuMPContainer) = map(copy, d)
540-
function Base.copy(d::JuMPContainer, new_model::Model)
541-
new_d = map(x -> copy(x, new_model), d)
542-
new_d.meta[:model] = new_model
543-
new_d
544-
end
545-
546536
##########################################################################
547537
# ConstraintRef
548538
# Reference to a constraint for retrieving solution info
@@ -760,20 +750,6 @@ function Base.setindex!(m::JuMP.Model, value, name::Symbol)
760750
end
761751

762752
# usage warnings
763-
function mapcontainer_warn(f, x::JuMPContainer, var_or_expr)
764-
isempty(x) && return
765-
v = first(values(x))
766-
m = v.m
767-
m.map_counter += 1
768-
if m.map_counter > 400
769-
# It might not be f that was called the 400 first times but most probably it is f
770-
Base.warn_once("$f has been called on a collection of $(var_or_expr)s a large number of times. For performance reasons, this should be avoided. Instead of $f(x)[a,b,c], use $f(x[a,b,c]) to avoid temporary allocations.")
771-
end
772-
end
773-
mapcontainer_warn(f, x::JuMPContainer{Variable}) = mapcontainer_warn(f, x, "variable")
774-
mapcontainer_warn{E}(f, x::JuMPContainer{E}) = mapcontainer_warn(f, x, "expression")
775-
getvalue_warn(x::JuMPContainer) = nothing
776-
777753
function operator_warn(lhs::AffExpr,rhs::AffExpr)
778754
if length(lhs.vars) > 50 || length(rhs.vars) > 50
779755
if length(lhs.vars) > 1
@@ -821,6 +797,7 @@ Base.ndims(::JuMPTypes) = 0
821797

822798

823799
##########################################################################
800+
include("containers.jl")
824801
include("operators.jl")
825802
# include("writers.jl")
826803
include("macros.jl")

src/JuMPArray.jl

+189-71
Original file line numberDiff line numberDiff line change
@@ -3,96 +3,214 @@
33
# License, v. 2.0. If a copy of the MPL was not distributed with this
44
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
55

6-
immutable JuMPArray{T,N,NT} <: JuMPContainer{T,N}
7-
innerArray::Array{T,N}
8-
indexsets::NT
9-
lookup::NTuple{N,Any}
10-
meta::Dict{Symbol,Any}
6+
# JuMPArray is inspired by the AxisArrays package.
7+
# JuMPArray can be replaced with AxisArray once integer indices are no longer
8+
# a special case. See discussions at:
9+
# https://github.com/JuliaArrays/AxisArrays.jl/issues/117
10+
# https://github.com/JuliaArrays/AxisArrays.jl/issues/84
11+
12+
13+
struct JuMPArray{T,N,Ax} <: AbstractArray{T,N}
14+
data::Array{T,N}
15+
axes::Ax
16+
lookup::Vector{Dict} # TODO: correctly type return type of the Dict as Int
1117
end
1218

13-
@generated function JuMPArray{T,N}(innerArray::Array{T,N}, indexsets::NTuple{N,Any})
14-
dicttuple = Expr(:tuple)
19+
export JuMPArray
20+
21+
function JuMPArray(data::Array{T,N}, axs...) where {T,N}
22+
lookup = Vector{Dict}(N)
1523
for i in 1:N
16-
inner = quote
17-
idxset = indexsets[$i]
18-
ret = Dict{eltype(idxset), Int}()
19-
end
20-
tupelem = indexsets.parameters[i]
21-
if !(tupelem == UnitRange{Int} || tupelem == StepRange{Int})
22-
inner = quote
23-
$inner
24-
cnt = 1
25-
for x in idxset
26-
ret[x] = cnt
27-
cnt += 1
28-
end
29-
ret
24+
d = Dict{eltype(axs[i]),Int}()
25+
cnt = 1
26+
for el in axs[i]
27+
if haskey(d, el)
28+
error("Repeated index $el. Index sets must have unique elements.")
3029
end
30+
d[el] = cnt
31+
cnt += 1
3132
end
32-
push!(dicttuple.args, inner)
33+
lookup[i] = d
3334
end
34-
:(JuMPArray(innerArray, indexsets, $dicttuple, Dict{Symbol,Any}()))
35+
return JuMPArray(data, axs, lookup)
3536
end
3637

37-
Base.getindex(d::JuMPArray, ::Colon) = d.innerArray[:]
38+
# TODO: use generated function to make this fast
39+
function to_index(A::JuMPArray{T,N}, idx...) where {T,N}
40+
return tuple((isa(i,Colon) ? Colon() : (k <= N ? A.lookup[k][i] : (((i == 1) ? 1 : error("invalid index $i")))) for (k,i) in enumerate(idx))...)
41+
end
3842

39-
@generated function Base.getindex{T,N,NT}(d::JuMPArray{T,N,NT}, idx...)
40-
if N != length(idx)
41-
error("Indexed into a JuMPArray with $(length(idx)) indices (expected $N indices)")
43+
# TODO: use generated function to make this fast and type stable
44+
# TODO: better error (or just handle correctly) when user tries to index with a range like a:b
45+
# The only kind of slicing we support is dropping a dimension with colons
46+
function Base.getindex(A::JuMPArray{T}, idx...) where {T}
47+
if Colon() in idx
48+
JuMPArray(A.data[to_index(A,idx...)...], (ax for (i,ax) in enumerate(A.axes) if idx[i] == Colon())...)
49+
else
50+
return A.data[to_index(A,idx...)...]::T
4251
end
43-
Expr(:call, :getindex, :(d.innerArray), _to_cartesian(d,NT,idx)...)
4452
end
53+
Base.getindex(A::JuMPArray, idx::CartesianIndex) = A.data[idx]
54+
55+
Base.setindex!(A::JuMPArray, v, idx...) = A.data[to_index(A,idx...)...] = v
56+
Base.setindex!(A::JuMPArray, v, idx::CartesianIndex) = A.data[idx] = v
57+
58+
# AbstractArray interface
59+
60+
Base.linearindices(A::JuMPArray) = error("JuMPArray does not support this operation.")
61+
Base.size(A::JuMPArray) = error("JuMPArray does not define this operation")
62+
Base.indices(A::JuMPArray) = A.axes
4563

46-
@generated function Base.setindex!{T,N,NT}(d::JuMPArray{T,N,NT}, v, idx...)
47-
if N != length(idx)
48-
error("Indexed into a JuMPArray with $(length(idx)) indices (expected $N indices)")
64+
# Arbitrary typed indices. Linear indexing not supported.
65+
struct IndexAnyCartesian <: Base.IndexStyle end
66+
Base.IndexStyle(::Type{JuMPArray{T,N,Ax}}) where {T,N,Ax} = IndexAnyCartesian()
67+
68+
Base.broadcast(f::Function, A::JuMPArray) = JuMPArray(broadcast(f, A.data), A.axes, A.lookup)
69+
70+
Base.isempty(A::JuMPArray) = isempty(A.data)
71+
72+
function Base.isassigned(A::JuMPArray, idx...)
73+
try
74+
to_index(idx...)
75+
return true
76+
catch
77+
return false
78+
end
79+
end
80+
# For ambiguity
81+
function Base.isassigned(A::JuMPArray, idx::Int...)
82+
try
83+
to_index(idx...)
84+
return true
85+
catch
86+
return false
4987
end
50-
Expr(:call, :setindex!, :(d.innerArray), :v, _to_cartesian(d,NT,idx)...)
5188
end
5289

53-
function _to_cartesian(d,NT,idx...)
54-
indexing = Any[]
55-
for (i,S) in enumerate(NT.parameters)
56-
idxtype = idx[1][i]
57-
if S == UnitRange{Int}
58-
if idxtype == Colon
59-
# special stuff
60-
push!(indexing, Colon())
61-
elseif idxtype <: Range
62-
push!(indexing, quote
63-
rng = d.indexsets[$i]
64-
I = idx[$i]
65-
I - (start(rng) - 1)
66-
end)
67-
else
68-
push!(indexing, quote
69-
rng = d.indexsets[$i]
70-
I = idx[$i]
71-
first(rng) <= I <= last(rng) || error("Failed attempt to index JuMPArray along dimension $($i): $I$(d.indexsets[$i])")
72-
I - (start(rng) - 1)
73-
end)
74-
end
75-
elseif S == StepRange{Int}
76-
if idx[1][i] == Colon
77-
push!(indexing, Colon())
90+
Base.eachindex(A::JuMPArray) = CartesianRange(size(A.data))
91+
92+
# TODO: similar
93+
94+
# Adapted printing from Julia's show.jl
95+
96+
# Copyright (c) 2009-2016: Jeff Bezanson, Stefan Karpinski, Viral B. Shah,
97+
# and other contributors:
98+
#
99+
# https://github.com/JuliaLang/julia/contributors
100+
#
101+
# Permission is hereby granted, free of charge, to any person obtaining
102+
# a copy of this software and associated documentation files (the
103+
# "Software"), to deal in the Software without restriction, including
104+
# without limitation the rights to use, copy, modify, merge, publish,
105+
# distribute, sublicense, and/or sell copies of the Software, and to
106+
# permit persons to whom the Software is furnished to do so, subject to
107+
# the following conditions:
108+
#
109+
# The above copyright notice and this permission notice shall be
110+
# included in all copies or substantial portions of the Software.
111+
112+
function summaryio(io::IO, A::JuMPArray)
113+
_summary(io, A)
114+
for (k,ax) in enumerate(A.axes)
115+
print(io, " Dimension $k, ")
116+
show(IOContext(io, :limit=>true), ax)
117+
println(io)
118+
end
119+
print(io, "And data, a ", summary(A.data))
120+
end
121+
_summary(io, A::JuMPArray{T,N}) where {T,N} = println(io, "$N-dimensional JuMPArray{$T,$N,...} with index sets:")
122+
123+
function Base.summary(A::JuMPArray)
124+
io = IOBuffer()
125+
summaryio(io, A)
126+
String(io)
127+
end
128+
129+
function Base.showarray(io::IO, X::JuMPArray, repr::Bool = true; header = true)
130+
repr = false
131+
#if repr && ndims(X) == 1
132+
# return Base.show_vector(io, X, "[", "]")
133+
#end
134+
if !haskey(io, :compact)
135+
io = IOContext(io, :compact => true)
136+
end
137+
if !repr && get(io, :limit, false) && eltype(X) === Method
138+
# override usual show method for Vector{Method}: don't abbreviate long lists
139+
io = IOContext(io, :limit => false)
140+
end
141+
(!repr && header) && print(io, summary(X))
142+
if !isempty(X.data)
143+
(!repr && header) && println(io, ":")
144+
if ndims(X.data) == 0
145+
if isassigned(X.data)
146+
return show(io, X.data[])
78147
else
79-
push!(indexing, quote
80-
rng = $(d.indexsets[i])
81-
I = idx[$i]
82-
first(rng) <= I <= last(rng) || error("Failed attempt to index JuMPArray along dimension $($i): $I$(d.indexsets[$i])")
83-
dv, rv = divrem(I - start(rng), step(rng))
84-
rv == 0 || error("Failed attempt to index JuMPArray along dimension $($i): $I$(d.indexsets[$i])")
85-
dv + 1
86-
end)
148+
return print(io, undef_ref_str)
87149
end
150+
end
151+
#if repr
152+
# if ndims(X.data) <= 2
153+
# Base.print_matrix_repr(io, X)
154+
# else
155+
# show_nd(io, X, print_matrix_repr, false)
156+
# end
157+
#else
158+
punct = (" ", " ", "")
159+
if ndims(X.data) <= 2
160+
Base.print_matrix(io, X.data, punct...)
88161
else
89-
push!(indexing, quote
90-
if !haskey(d.lookup[$i],idx[$i])
91-
error("Failed attempt to index JuMPArray along dimension $($i): $(idx[$i])$(d.indexsets[$i])")
162+
show_nd(io, X,
163+
(io, slice) -> Base.print_matrix(io, slice, punct...),
164+
!repr)
165+
end
166+
#end
167+
elseif repr
168+
Base.repremptyarray(io, X.data)
169+
end
170+
end
171+
172+
# n-dimensional arrays
173+
function show_nd(io::IO, a::JuMPArray, print_matrix, label_slices)
174+
limit::Bool = get(io, :limit, false)
175+
if isempty(a)
176+
return
177+
end
178+
tailinds = Base.tail(Base.tail(indices(a.data)))
179+
nd = ndims(a)-2
180+
for I in CartesianRange(tailinds)
181+
idxs = I.I
182+
if limit
183+
for i = 1:nd
184+
ii = idxs[i]
185+
ind = tailinds[i]
186+
if length(ind) > 10
187+
if ii == ind[4] && all(d->idxs[d]==first(tailinds[d]),1:i-1)
188+
for j=i+1:nd
189+
szj = size(a,j+2)
190+
indj = tailinds[j]
191+
if szj>10 && first(indj)+2 < idxs[j] <= last(indj)-3
192+
@goto skip
193+
end
194+
end
195+
#println(io, idxs)
196+
print(io, "...\n\n")
197+
@goto skip
198+
end
199+
if ind[3] < ii <= ind[end-3]
200+
@goto skip
201+
end
92202
end
93-
d.lookup[$i][idx[$i]]::Int
94-
end)
203+
end
204+
end
205+
if label_slices
206+
print(io, "[:, :, ")
207+
for i = 1:(nd-1); show(io, a.axes[i+2][idxs[i]]); print(io,", "); end
208+
show(io, a.axes[end][idxs[end]])
209+
println(io, "] =")
95210
end
211+
slice = view(a.data, indices(a.data,1), indices(a.data,2), idxs...)
212+
Base.print_matrix(io, slice)
213+
print(io, idxs == map(last,tailinds) ? "" : "\n\n")
214+
@label skip
96215
end
97-
indexing
98216
end

0 commit comments

Comments
 (0)