-
-
Notifications
You must be signed in to change notification settings - Fork 396
/
callbacks.jl
343 lines (306 loc) · 10.8 KB
/
callbacks.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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# Copyright 2017, 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/.
export addlazycallback, addcutcallback, addheuristiccallback, addinfocallback
abstract type JuMPCallback end
mutable struct LazyCallback <: JuMPCallback
f::Function
fractional::Bool
end
mutable struct CutCallback <: JuMPCallback
f::Function
end
mutable struct HeuristicCallback <: JuMPCallback
f::Function
end
mutable struct InfoCallback <: JuMPCallback
f::Function
when::Symbol
end
mutable struct StopTheSolver end
mutable struct CallbackAbort <: Exception end
export CallbackAbort
Base.copy(c::T) where {T<:JuMPCallback} = T(copy(c))
Base.copy(c::LazyCallback) = LazyCallback(copy(c.f), c.fractional)
function addlazycallback(m::Model, f::Function; fractional::Bool=false)
m.internalModelLoaded = false
push!(m.callbacks, LazyCallback(f,fractional))
end
function addcutcallback(m::Model, f::Function)
m.internalModelLoaded = false
push!(m.callbacks, CutCallback(f))
end
function addheuristiccallback(m::Model, f::Function)
m.internalModelLoaded = false
push!(m.callbacks, HeuristicCallback(f))
end
function _unspecifiedstate()
warn("""Info Callbacks without a when clause will currently default
to fire only in the :Intermediate state to preserve its behavior
with previous versions of JuMP.
This behavior will be deprecated in subsequent versions of JuMP, so
please rewrite all invocations of addinfocallbacks(m, f) to
addinfocallbacks(m, f, when = :Intermediate) instead.
""")
:Intermediate
end
function addinfocallback(m::Model, f::Function; when::Symbol = _unspecifiedstate())
m.internalModelLoaded = false
push!(m.callbacks, InfoCallback(f, when))
end
function lazycallback(d::MathProgBase.MathProgCallbackData, m::Model, cbs::Vector{LazyCallback})
state = MathProgBase.cbgetstate(d)
@assert state == :MIPSol || state == :MIPNode
anyfrac = mapreduce(|, cbs) do cb
cb.fractional
end
if state == :MIPSol
MathProgBase.cbgetmipsolution(d,m.colVal)
elseif anyfrac
MathProgBase.cbgetlpsolution(d,m.colVal)
else
return :Continue
end
try
for cb in cbs
if state == :MIPSol || cb.fractional
ret = cb.f(d)
if ret === StopTheSolver
return :Exit
end
end
end
catch y
if isa(y, CallbackAbort)
Base.warn_once("Throwing CallbackAbort() from a callback is deprecated. Use \"return JuMP.StopTheSolver\" instead.")
return :Exit
else
rethrow(y)
end
end
:Continue
end
function attach_callbacks(m::Model, cbs::Vector{LazyCallback})
cb = d -> lazycallback(d,m,cbs)
if applicable(MathProgBase.setlazycallback!, m.internalModel, cb)
MathProgBase.setlazycallback!(m.internalModel, cb)
else
error("Solver does not support lazy callbacks")
end
end
function cutcallback(d::MathProgBase.MathProgCallbackData, m::Model, cbs::Vector{CutCallback})
state = MathProgBase.cbgetstate(d)
@assert state == :MIPNode
MathProgBase.cbgetlpsolution(d,m.colVal)
try
for cb in cbs
ret = cb.f(d)
if ret === StopTheSolver
return :Exit
end
end
catch y
if isa(y, CallbackAbort)
return :Exit
else
rethrow(y)
end
end
:Continue
end
function attach_callbacks(m::Model, cbs::Vector{CutCallback})
cb = d -> cutcallback(d,m,cbs)
if applicable(MathProgBase.setcutcallback!, m.internalModel, cb)
MathProgBase.setcutcallback!(m.internalModel, cb)
else
error("Solver does not support cut callbacks")
end
end
function heurcallback(d::MathProgBase.MathProgCallbackData, m::Model, cbs::Vector{HeuristicCallback})
state = MathProgBase.cbgetstate(d)
@assert state == :MIPNode
MathProgBase.cbgetlpsolution(d,m.colVal)
try
for cb in cbs
ret = cb.f(d)
if ret === StopTheSolver
return :Exit
end
end
catch y
if isa(y, CallbackAbort)
return :Exit
else
rethrow(y)
end
end
:Continue
end
function attach_callbacks(m::Model, cbs::Vector{HeuristicCallback})
cb = d -> heurcallback(d,m,cbs)
if applicable(MathProgBase.setheuristiccallback!, m.internalModel, cb)
MathProgBase.setheuristiccallback!(m.internalModel, cb)
else
error("Solver does not support heuristic callbacks")
end
end
function infocallback(d::MathProgBase.MathProgCallbackData, m::Model, cbs::Vector{InfoCallback})
state = MathProgBase.cbgetstate(d)
if state == :MIPSol
MathProgBase.cbgetmipsolution(d,m.colVal)
elseif state == :MIPNode
MathProgBase.cbgetlpsolution(d,m.colVal)
end
try
for cb in cbs
if cb.when == state
ret = cb.f(d)
if ret === StopTheSolver
return :Exit
end
end
end
catch y
if isa(y, CallbackAbort)
return :Exit
else
rethrow(y)
end
end
:Continue
end
function attach_callbacks(m::Model, cbs::Vector{InfoCallback})
cb = d -> infocallback(d,m,cbs)
if applicable(MathProgBase.setinfocallback!, m.internalModel, cb)
MathProgBase.setinfocallback!(m.internalModel, cb)
else
error("Solver does not support info callbacks")
end
end
function registercallbacks(m::Model)
isempty(m.callbacks) && return # might as well avoid allocating the indexedVector
cbtypes = unique(map(typeof, m.callbacks))
for typ in cbtypes
cbs::Vector{typ} = filter(m.callbacks) do cb
isa(cb, typ)
end
attach_callbacks(m, cbs)
end
# prepare storage for callbacks
m.indexedVector = IndexedVector(Float64, m.numCols)
end
# TODO: Should this be somewhere else?
const sensemap = Dict(:(<=) => '<', :(==) => '=', :(>=) => '>')
## Lazy constraints
export @lazyconstraint
macro lazyconstraint(args...)
cbdata = esc(args[1])
x = args[2]
extra = vcat(args[3:end]...)
# separate out keyword arguments
kwargs = filter(ex->isexpr(ex,:(=)), extra) # filtering expressions corresponding to kw args specs
extra = filter(ex->!isexpr(ex,:(=)), extra) # others
localcut_val = false # by default, the lazy constraint is global
for ex in kwargs
kwarg = ex.args[1]
if kwarg == :localcut
localcut_val = esc(ex.args[2]) # excepted if otherwise specified ...
else error("in @lazyconstraint($(join(args,','))), invalid keyword argument: $(kwarg)")
end
end
if isexpr(x, :call) && length(x.args) == 3 # simple comparison
lhs = :($(x.args[2]) - $(x.args[3])) # move everything to the lhs
newaff, parsecode = parseExprToplevel(lhs, :aff)
sense, vectorized = _canonicalize_sense(x.args[1])
code = quote
aff = zero(AffExpr)
$parsecode
constr = constructconstraint!($newaff, $(quot(sense)))
end
if vectorized
return quote
$code
for con in constr
addlazyconstraint($cbdata, con, localcut=$localcut_val)
end
end
else
return quote
$code
addlazyconstraint($cbdata, constr, localcut=$localcut_val)
end
end
else
error("Syntax error in @lazyconstraint, expected one-sided comparison.")
end
end
function addlazyconstraint(cbdata::MathProgBase.MathProgCallbackData, constr::LinearConstraint; localcut::Bool=false)
if length(constr.terms.vars) == 0
MathProgBase.cbaddlazy!(cbdata, Cint[], Float64[], sensemap[sense(constr)], rhs(constr))
return
end
assert_isfinite(constr.terms)
m::Model = constr.terms.vars[1].m
indices, coeffs = merge_duplicates(Cint, constr.terms, m.indexedVector, m)
if localcut
MathProgBase.cbaddlazylocal!(cbdata, indices, coeffs, sensemap[sense(constr)], rhs(constr))
else
MathProgBase.cbaddlazy!(cbdata, indices, coeffs, sensemap[sense(constr)], rhs(constr))
end
end
addlazyconstraint(cbdata::MathProgBase.MathProgCallbackData,constr::QuadConstraint; localcut::Bool=false) = error("Quadratic lazy constraints are not supported.")
## User cuts
export addusercut
export @usercut
macro usercut(args...)
cbdata = esc(args[1])
x = args[2]
extra = vcat(args[3:end]...)
# separate out keyword arguments
kwargs = filter(ex->isexpr(ex,:(=)), extra) # filtering expressions corresponding to kw args specs
extra = filter(ex->!isexpr(ex,:(=)), extra) # others
localcut_val = false # by default, the user cut is global
for ex in kwargs
kwarg = ex.args[1]
if kwarg == :localcut
localcut_val = esc(ex.args[2]) # excepted if otherwise specified ...
else error("in @usercut($(join(args,','))), invalid keyword argument: $(kwarg)")
end
end
#cbdata = esc(cbdata)
if isexpr(x, :call) && length(x.args) == 3 # simple comparison
lhs = :($(x.args[2]) - $(x.args[3])) # move everything to the lhs
newaff, parsecode = parseExprToplevel(lhs, :aff)
sense, vectorized = _canonicalize_sense(x.args[1])
vectorized && error("Cannot add vectorized constraint in cut callback")
quote
aff = zero(AffExpr)
$parsecode
constr = constructconstraint!($newaff, $(quot(sense)))
addusercut($cbdata, constr, localcut=$localcut_val)
end
else
error("Syntax error in @usercut, expected one-sided comparison.")
end
end
function addusercut(cbdata::MathProgBase.MathProgCallbackData, constr::LinearConstraint; localcut::Bool=false)
if length(constr.terms.vars) == 0
MathProgBase.cbaddcut!(cbdata, Cint[], Float64[], sensemap[sense(constr)], rhs(constr))
return
end
assert_isfinite(constr.terms)
m::Model = constr.terms.vars[1].m
indices, coeffs = merge_duplicates(Cint, constr.terms, m.indexedVector, m)
if localcut
MathProgBase.cbaddcutlocal!(cbdata, indices, coeffs, sensemap[sense(constr)], rhs(constr))
else
MathProgBase.cbaddcut!(cbdata, indices, coeffs, sensemap[sense(constr)], rhs(constr))
end
end
## User heuristic
export addsolution, setsolutionvalue
addsolution(cbdata::MathProgBase.MathProgCallbackData) = MathProgBase.cbaddsolution!(cbdata)
function setsolutionvalue(cbdata::MathProgBase.MathProgCallbackData, v::Variable, x)
MathProgBase.cbsetsolutionvalue!(cbdata, convert(Cint, v.col), x)
end