forked from jump-dev/JuMP.jl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
JuMPContainer.jl
283 lines (237 loc) · 8.22 KB
/
JuMPContainer.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# Copyright 2016, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
using Base.Meta
# multivarate "dictionary" used for collections of variables/constraints
abstract JuMPContainer{T,N}
include("JuMPArray.jl")
type IndexPair
idxvar
idxset
end
type JuMPDict{T,N} <: JuMPContainer{T,N}
tupledict::Dict{NTuple{N,Any},T}
meta::Dict{Symbol,Any}
JuMPDict() = new(Dict{NTuple{N,Any},T}(), Dict{Symbol,Any}())
end
function JuMPDict{T,N}(d::Dict{NTuple{N,Any},T})
tmp = JuMPDict{T,N}()
tmp.tupledict = d
tmp
end
function JuMPDict{T,N}(d::Dict{NTuple{N,Any},T}, meta::Dict{Symbol,Any})
tmp = JuMPDict{T,N}()
tmp.tupledict = d
tmp.meta = meta
tmp
end
type JuMPContainerData
name
indexsets
indexexprs::Vector{IndexPair}
condition
end
# Needed by getvaluewarn when called by _mapInner
getname(data::JuMPContainerData) = data.name
#JuMPDict{T,N}(name::AbstractString) =
# JuMPDict{T,N}(Dict{NTuple{N},T}(), name)
Base.getindex(d::JuMPDict, t...) = d.tupledict[t]
Base.setindex!(d::JuMPDict, value, t...) = (d.tupledict[t] = value)
Base.map(f::Function, d::JuMPArray) =
JuMPArray(map(f, d.innerArray), d.indexsets, d.lookup, d.meta)
function Base.map{T,N}(f::Function, d::JuMPDict{T,N})
ret = Base.return_types(f, Tuple{T})
R = (length(ret) == 1 ? ret[1] : Any)
x = JuMPDict{R,N}()
for (k,v) in d.tupledict
x.tupledict[k] = f(v)
end
return x
end
Base.isempty(d::JuMPContainer) = isempty(_innercontainer(d))
coloncheck(args::Number...) = nothing
function coloncheck(args...)
if any(t -> t == Colon(), args)
error("Colons not allowed as keys in JuMP containers.")
end
end
# generate and instantiate a type which is indexed by the given index sets
# the following types of index sets are allowed:
# 0:K -- range with compile-time starting index
# S -- general iterable set
function gendict(instancename,T,idxsets...)
N = length(idxsets)
truearray = true
for idxset in idxsets
s = isexpr(idxset,:escape) ? idxset.args[1] : idxset
if !(isexpr(s,:(:)) && length(s.args) == 2 && s.args[1] == 1)
truearray = false
break
end
end
sizes = Expr(:tuple, [:(length($rng)) for rng in idxsets]...)
if truearray
:($instancename = Array($T, $sizes...))
else
indexsets = Expr(:tuple, idxsets...)
:($instancename = JuMPArray(Array($T, $sizes...), $indexsets))
end
end
pushmeta!(x::JuMPContainer, sym::Symbol, val) = (x.meta[sym] = val)
getmeta(x::JuMPContainer, sym::Symbol) = x.meta[sym]
# duck typing approach -- if eltype(innerArray) doesn't support accessor, will fail
for accessor in (:getdual, :getlowerbound, :getupperbound, :getvalue)
@eval $accessor(x::AbstractArray) = map($accessor,x)
end
# With JuMPContainer, we take care in _mapInner of the warning if NaN values are returned
# by the accessor so we use the inner accessor that does not generate warnings
for (accessor, inner) in ((:getdual, :_getDual), (:getlowerbound, :getlowerbound), (:getupperbound, :getupperbound), (:getvalue, :_getValue))
@eval $accessor(x::JuMPContainer) = _map($inner,x)
end
_similar(x::Array) = Array(Float64,size(x))
_similar{T}(x::Dict{T}) = Dict{T,Float64}()
_innercontainer(x::JuMPArray) = x.innerArray
_innercontainer(x::JuMPDict) = x.tupledict
# Warning for getter returning NaN
function _warnnan(f, data)
if f === _getValue
getvaluewarn(data)
elseif f === _getDual
getdualwarn(data)
end
end
function _mapInner(f, x::JuMPContainer)
vars = _innercontainer(x)
vals = _similar(vars)
data = printdata(x)
warnedyet = false
for I in eachindex(vars)
tmp = f(vars[I])
if isnan(tmp) && !warnedyet
_warnnan(f, data.name)
warnedyet = true
end
vals[I] = tmp
end
vals
end
JuMPContainer_from(x::JuMPDict,inner) = JuMPDict(inner)
JuMPContainer_from(x::JuMPArray,inner) = JuMPArray(inner, x.indexsets)
# The name _map is used instead of map so that this function is called instead of map(::Function, ::JuMPArray)
function _map(f, x::JuMPContainer)
mapcontainer_warn(f, x)
ret = JuMPContainer_from(x, _mapInner(f, x))
# I guess copy!(::Dict, ::Dict) isn't defined, so...
for (key,val) in x.meta
ret.meta[key] = val
end
m = getmeta(x, :model)
# cache indexing info for new container for printing purposes
m.varData[ret] = printdata(x)
ret
end
# delegate zero-argument functions
for f in (:(Base.ndims), :(Base.length), :(Base.abs))
@eval $f(x::JuMPArray) = $f(x.innerArray)
end
Base.length(x::JuMPDict) = length(x.tupledict)
Base.ndims{T,N}(x::JuMPDict{T,N}) = N
Base.abs(x::JuMPDict) = map(abs, x)
# avoid dangerous behavior with "end" (#730)
Base.endof(x::JuMPArray) = error("endof() (and \"end\" syntax) not implemented for JuMPArray objects.")
Base.size(x::JuMPArray) = error(string("size (and \"end\" syntax) not implemented for JuMPArray objects.",
"Use JuMP.size if you want to access the dimensions."))
Base.size(x::JuMPArray,k) = error(string("size (and \"end\" syntax) not implemented for JuMPArray objects.",
" Use JuMP.size if you want to access the dimensions."))
size(x::JuMPArray) = size(x.innerArray)
size(x::JuMPArray,k) = size(x.innerArray,k)
# for uses of size() within JuMP
size(x) = Base.size(x)
size(x,k) = Base.size(x,k)
# delegate one-argument functions
Base.issymmetric(x::JuMPArray) = issymmetric(x.innerArray)
Base.eltype{T}(x::JuMPContainer{T}) = T
Base.full(x::JuMPContainer) = x
# keys/vals iterations for JuMPContainers
Base.keys(d::JuMPDict) = keys(d.tupledict)
Base.values(d::JuMPDict) = values(d.tupledict)
Base.keys(d::JuMPArray) = KeyIterator(d)
Base.values(d::JuMPArray) = ValueIterator(d.innerArray)
# Wrapper type so that you can't access the values directly
type ValueIterator{T,N}
x::Array{T,N}
end
Base.start(it::ValueIterator) = start(it.x)
Base.next(it::ValueIterator, k) = next(it.x, k)
Base.done(it::ValueIterator, k) = done(it.x, k)
Base.length(it::ValueIterator) = length(it.x)
type KeyIterator{JA<:JuMPArray}
x::JA
dim::Int
next_k_cache::Array{Any,1}
function KeyIterator(d)
n = ndims(d.innerArray)
new(d, n, Array(Any, n+1))
end
end
KeyIterator{JA}(d::JA) = KeyIterator{JA}(d)
function indexability(x::JuMPArray)
for i in 1:length(x.indexsets)
if !method_exists(getindex, (typeof(x.indexsets[i]),))
return false
end
end
return true
end
function Base.start(it::KeyIterator)
if indexability(it.x)
return start(it.x.innerArray)
else
return notindexable_start(it.x)
end
end
@generated function notindexable_start{T,N,NT}(x::JuMPArray{T,N,NT})
quote
$(Expr(:tuple, 0, [:(start(x.indexsets[$i])) for i in 1:N]...))
end
end
@generated function _next{T,N,NT}(x::JuMPArray{T,N,NT}, k::Tuple)
quote
$(Expr(:tuple, [:(next(x.indexsets[$i], k[$i+1])[1]) for i in 1:N]...))
end
end
function Base.next(it::KeyIterator, k::Tuple)
cartesian_key = _next(it.x, k)
pos = -1
for i in 1:it.dim
if !done(it.x.indexsets[i], next(it.x.indexsets[i], k[i+1])[2] )
pos = i
break
end
end
if pos == - 1
it.next_k_cache[1] = 1
return cartesian_key, tuple(it.next_k_cache...)
end
it.next_k_cache[1] = 0
for i in 1:it.dim
if i < pos
it.next_k_cache[i+1] = start(it.x.indexsets[i])
elseif i == pos
it.next_k_cache[i+1] = next(it.x.indexsets[i], k[i+1])[2]
else
it.next_k_cache[i+1] = k[i+1]
end
end
cartesian_key, tuple(it.next_k_cache...)
end
Base.done(it::KeyIterator, k::Tuple) = (k[1] == 1)
@generated __next{T,N,NT}(x::JuMPArray{T,N,NT}, k::Integer) =
quote
subidx = ind2sub(size(x),k)
$(Expr(:tuple, [:(x.indexsets[$i][subidx[$i]]) for i in 1:N]...)), next(x.innerArray,k)[2]
end
Base.next(it::KeyIterator, k) = __next(it.x,k::Integer)
Base.done(it::KeyIterator, k) = done(it.x.innerArray, k::Integer)
Base.length(it::KeyIterator) = length(it.x.innerArray)