Skip to content

Commit

Permalink
Merge pull request #814 from yeesian/infocb
Browse files Browse the repository at this point in the history
Permit info callbacks in states other than MIPInfo
  • Loading branch information
joehuchette authored Sep 12, 2016
2 parents 2555955 + 62743ae commit 616deee
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 12 deletions.
19 changes: 17 additions & 2 deletions doc/callbacks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ including::
Informational Callbacks
^^^^^^^^^^^^^^^^^^^^^^^

Sometimes it can be useful to track solver progress without actually changing the algorithm by adding cuts or heuristic solutions. In these cases, informational callbacks can be added, wherein statistics can be tracked via the ``cbget`` functions discussed in the previous section. Informational callbacks are added to a JuMP model with the ``addinfocallback(m::Model, f::Function)`` function.
Sometimes it can be useful to track solver progress without actually changing the algorithm by adding cuts or heuristic solutions. In these cases, informational callbacks can be added, wherein statistics can be tracked via the ``cbget`` functions discussed in the previous section. Informational callbacks are added to a JuMP model with the ``addinfocallback(m::Model, f::Function; when::Symbol)`` function, where the `when` argument should be one of ``:MIPNode``, ``:MIPSol`` or ``:Intermediate`` (listed under ``cbgetstate()`` in the `MathProgBase documentation <https://mathprogbasejl.readthedocs.io/en/latest/lpqcqp.html#cbgetstate>`_)

For a simple example, we can add a function that tracks the best bound and incumbent objective value as the solver progresses through the branch-and-bound tree::

Expand All @@ -308,7 +308,7 @@ For a simple example, we can add a function that tracks the best bound and incum
bestbound = MathProgBase.cbgetbestbound(cb)
push!(bbdata, NodeData(time(),node,obj,bestbound))
end
addinfocallback(m, infocallback)
addinfocallback(m, infocallback, when = :Intermediate)

solve(m)

Expand All @@ -321,6 +321,21 @@ For a simple example, we can add a function that tracks the best bound and incum
end
end

For a second example, we can add a function that tracks the intermediate solutions at each integer-feasible solution in the Branch-and-Bound tree::

solutionvalues = Vector{Float64}[]

# build model ``m`` up here

function infocallback(cb)
push!(solutionvalues, JuMP.getvalue(x))
end
addinfocallback(m, infocallback, when = :MIPSol)

solve(m)

# all the intermediate solutions are now stored in `solutionvalues`


Code Design Considerations
^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
28 changes: 24 additions & 4 deletions src/callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type HeuristicCallback <: JuMPCallback
end
type InfoCallback <: JuMPCallback
f::Function
when::Symbol
end

type CallbackAbort <: Exception end
Expand All @@ -38,9 +39,22 @@ function addheuristiccallback(m::Model, f::Function)
m.internalModelLoaded = false
push!(m.callbacks, HeuristicCallback(f))
end
function addinfocallback(m::Model, f::Function)

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))
push!(m.callbacks, InfoCallback(f, when))
end

function lazycallback(d::MathProgBase.MathProgCallbackData, m::Model, cbs::Vector{LazyCallback})
Expand Down Expand Up @@ -138,10 +152,16 @@ end

function infocallback(d::MathProgBase.MathProgCallbackData, m::Model, cbs::Vector{InfoCallback})
state = MathProgBase.cbgetstate(d)
@assert state == :MIPInfo
if state == :MIPSol
MathProgBase.cbgetmipsolution(d,m.colVal)
elseif state == :MIPNode
MathProgBase.cbgetlpsolution(d,m.colVal)
end
try
for cb in cbs
cb.f(d)
if cb.when == state
cb.f(d)
end
end
catch y
if isa(y, CallbackAbort)
Expand Down
22 changes: 16 additions & 6 deletions test/callback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,9 @@ for infosolver in info_solvers
context("With solver $(typeof(infosolver))") do
nodes = Int[]
objs = Float64[]
objvals = Float64[]
bestbounds = Float64[]
entered = [false,false]
entered = fill(false, 2)

N = 10000
include(joinpath("data","informational.jl"))
Expand All @@ -226,16 +227,22 @@ context("With solver $(typeof(infosolver))") do
@objective(mod, Max, dot(r1,x))
@constraint(mod, c[i=1:10], dot(r2[i],x) <= rhs[i]*N/10)
# Test that solver fills solution correctly
function myinfo(cb)
function myinfo1(cb)
@assert MathProgBase.cbgetstate(cb) == :Intermediate
entered[1] = true
push!(nodes, MathProgBase.cbgetexplorednodes(cb))
push!(objs, MathProgBase.cbgetobj(cb))
push!(bestbounds, MathProgBase.cbgetbestbound(cb))
end
addinfocallback(mod, myinfo)
addinfocallback(mod, cb -> (entered[2] = true))
function myinfo2(cb)
@assert MathProgBase.cbgetstate(cb) == :MIPSol
push!(objvals, dot(r1,JuMP.getvalue(x[:])))
end
addinfocallback(mod, myinfo1, when = :Intermediate)
addinfocallback(mod, myinfo2, when = :MIPSol)
addinfocallback(mod, cb -> (entered[2] = true), when = :Intermediate)
@fact solve(mod) --> :Optimal
@fact entered --> [true,true]
@fact entered --> fill(true, 2)
mono_node, mono_obj, mono_bestbound = true, true, true
for n in 2:length(nodes)
mono_node &= (nodes[n-1] <= nodes[n] + 1e-8)
Expand All @@ -247,6 +254,9 @@ context("With solver $(typeof(infosolver))") do
@fact mono_node --> true
@fact mono_obj --> true
@fact mono_bestbound --> true
for (s1,s2) in zip(objvals[1:end-1], objvals[2:end])
@fact s1 <= s2 --> true
end
end; end; end

facts("[callback] Callback exit on CallbackAbort") do
Expand Down Expand Up @@ -275,6 +285,6 @@ cbc && facts("[callback] Solver doesn't support callbacks") do
addheuristiccallback(mod, mycb)
@fact_throws ErrorException solve(mod)
mod = Model(solver=Cbc.CbcSolver())
addinfocallback(mod, mycb)
addinfocallback(mod, mycb, when = :Intermediate)
@fact_throws ErrorException solve(mod)
end
1 change: 1 addition & 0 deletions test/solvers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ if glp
push!(lazy_solvers, GLPKMathProgInterface.GLPKSolverMIP())
push!( cut_solvers, GLPKMathProgInterface.GLPKSolverMIP())
push!(heur_solvers, GLPKMathProgInterface.GLPKSolverMIP())
push!(info_solvers, GLPKMathProgInterface.GLPKSolverMIP())
end
# Quadratic support
quad_solvers = Any[]
Expand Down

0 comments on commit 616deee

Please sign in to comment.