Using optimizer callbacks within JuMP to terminate MIP solve

I’ve got a MIP problem in JuMP, for which I would like the branch-and-bound to terminate as soon as the incumbent reaches a particular value, or as soon as the lower bound reaches a (different) particular value.

I’ve found an example of how this can be done in Python (using callbacks) on Gurobi’s website.

https://support.gurobi.com/hc/en-us/articles/360047717291-How-do-I-use-callbacks-to-terminate-the-solver-

My understanding is that JuMP’s support for callbacks is limited, but certain solver-specific callbacks can be implemented. Before I spend too much time reading through source code, I would like to know if this is possible, and if any examples exist that could be used as a starting point.

Thanks.

Ideally, something like this. But I’m getting a segfault when I try to terminate. (Edit: I think this may have been due to Julia’s GC mixing with the early terminate. I can’t reproduce if I turn it off.)

using JuMP
import Gurobi

softlimit = 5
hardlimit = 100

model = direct_model(
    Gurobi.Optimizer(
        OutputFlag = 0,
        Cuts = 0,
        Presolve = 0,
        PreCrush = 1,
        Heuristics = 0,
    )
)
@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)

function softtime(cb_data, cb_where)
    if cb_where == Gurobi.CB_MIP
        @info("Executing callback")
        runtime = Gurobi.cbget_runtime(cb_data, cb_where)
        objbst = Gurobi.cbget_mip_objbst(cb_data, cb_where)
        objbnd = Gurobi.cbget_mip_objbnd(cb_data, cb_where)
        gap = abs((objbst - objbnd) / objbst)
        @info("Terminating at $(runtime) with $(gap)")
        Gurobi.terminate(backend(model).inner)
    end
    return
end

set_optimizer_attribute(model, "TimeLimit", hardlimit)
MOI.set(model, Gurobi.CallbackFunction(), softtime)
GC.enable(false)  # You might want this
optimize!(model)
GC.enable(true)  # You might want this
1 Like

Thanks Oscar.

I ran your code (both with and without the GC enabled) and it seemed to work fine, and I didn’t get a segmentation fault.

Was there a reason for the optimizer settings (no cuts, presolve or heuristics etc.)?

That’s just so it triggers the callback on a small problem. Otherwise the optimal solution is found at the root node before the branch and bound routine (and callbacks) starts to get called.

1 Like

I’ve got this working in my application; thanks again. However, for some reason, I wasn’t able to directly access the inner field of my model and had to use

Gurobi.terminate(backend(model).optimizer.model.inner)

instead.

It’s for this reason I used direct_model(Gurobi.Optimizer()) instead of Model(Gurobi.Optimizer).

In the second case, you have to fight your way through the layers of bridging and caching optimizers to reach .inner.

1 Like