Does callbacks affect to the selection of algorithm?
AnsweredHello, I am a PhD student actively using the Gurobi solver for my research. I have a question regarding callbacks in Julia.
When I solve an optimization problem with the following callback code, the solver produces different results compared to running without a callback. Specifically, the presolve results are different, which leads me to suspect that the solver might be using a different algorithm or settings.
Could you please clarify why this is happening? Also, could you recommend a proper way to implement a callback that does not negatively affect solver performance?
# ==================== Callback ====================
if solver == :gurobi
println("Setting up Gurobi callback...")
function incumbent_callback(cb_data)
status = callback_node_status(cb_data, model)
if status == MOI.CALLBACK_NODE_STATUS_INTEGER
obj_value = callback_value(cb_data, obj_oil)
elapsed = time() - t0
open(log_file, "a") do f
@printf(f, "%.6f,%.6f\n", elapsed, obj_value)
end
end
end
set_attribute(model, MOI.LazyConstraintCallback(), incumbent_callback)
end
# ======================================================Thanks a lot
-
Hi Taehyeon,
When using a lazy constraint callback, Gurobi requires setting
LazyConstraints=1. As described in the documentation for this parameter, this setting tells Gurobi to avoid model reductions and transformations that are incompatible with lazy constraints. In particular, as discussed in the article “What are dual reductions?”, this setting will disable dual reductions, which are often quite helpful. The disabling of these presolve reductions is the reason the presolved model differs and why the algorithm ultimately takes a different solution path.Before proceeding, I would like to note that the Julia/JuMP interface in Gurobi.jl is not developed or officially supported by us. That said, in the Gurobi.jl code here, you'll find that
LazyConstraints=1is automatically (and correctly) configured when a JuMP/MathOptInterface lazy constraint callback is set. In your Gurobi logging output, you should see:Non-default parameters: LazyConstraints 1In your example, however, I notice that your callback function is purely informational. It is not being used to reduce the feasible set of the problem, which is typically when lazy constraint callbacks are employed. You may find this example in JuMP's documentation for a Gurobi solver-dependent callback more applicable to your use case. Assuming you are interested in logging new incumbents (and not all integer-feasible solutions that are encountered, as in your current callback), I believe you could instead write your callback function using something similar to the following:
... import Gurobi ... function incumbent_callback(cb_data, cb_where::Cint) # Only log when a new incumbent solution is found if cb_where != Gurobi.GRB_CB_MIPSOL return end # Before querying callback_value, load primal variable values Gurobi.load_callback_variable_primal(cb_data, cb_where) obj_value = callback_value(cb_data, obj_oil) elapsed = time() - t0 open(log_file, "a") do f @printf(f, "%.6f,%.6f\n", elapsed, obj_value) end return endThis callback can then be set via
set_attribute(model, Gurobi.CallbackFunction(), incumbent_callback)With this callback, there would be no need to set
LazyConstraints=1since lazy constraints are not being used. The solution paths should be identical with and without such a callback. You can review the Callback Codes section of our documentation if your application requires checking differentcb_wherevalues.I hope this helps! Please let me know if you have additional questions related to Gurobi, and I'll try my best to answer them. If you have JuMP-specific questions, I'd highly recommend asking in the JuMP community forum.
1 -
I sincerely appreciate your insightful answer. Understanding the impact of callbacks on the solver's path is crucial for me. Your explanation was both clear and comprehensive. Thank you!
1
Please sign in to leave a comment.
Comments
2 comments