Reputation: 1896
I am sorry, this time I could not produce a MWE. I tried but could not.
In JuMP Julia, I wanted to know if it is possible to get both Heuristic and LazyConstraints Callbacks on integer nodes. On the following NOT MWE code:
function call_back_benders_two_opt(cb_data)
status = callback_node_status(cb_data, m)
if status != MOI.CALLBACK_NODE_STATUS_FRACTIONAL
@show status
end
if status == MOI.CALLBACK_NODE_STATUS_INTEGER
x_cb = zeros(Bool, n, n)
for i in 1:n
for j in i+1:n
x_cb[i,j] = Bool(round(callback_value(cb_data, x[i, j])))
end
end
if sum(x_cb) > 0
ŷ = [Bool(round(callback_value(cb_data, y[i,i]))) for i in 1:n]
ring_edges = create_ring_edges_lazy(cb_data, x, n)
is_valid = validate_solution_lazy(ring_edges, ŷ, n)
if is_valid
x′, y_sp, sp_cost = compute_sp_res(x_cb, ŷ, V, n, tildeV, r, s)
hubs, master_cost = compute_master_res(x_cb, ŷ, V, n, tildeV, o, r)
x_opt = run_two_opt_wiki(x_cb, x′, hubs, sp_cost+master_cost, pars, n, r, rp, tildeV)[1]
for i in V
status = MOI.submit(
m, MOI.HeuristicSolution(cb_data), [x[i,j] for j in i+1:n], [x_opt[i,j] for j in i+1:n]
)
end
println("I submitted a heuristic solution, and the status was: ", status)
end
end
end
MOI.set(m, MOI.HeuristicCallback(), call_back_benders_two_opt)
MOI.set(m, MOI.LazyConstraintCallback(), call_back_benders)
My problem is that the Heuristic Callback
is only called on Fractional nodes. Indeed line @show status
never prints meaning that status
is always MOI.CALLBACK_NODE_STATUS_FRACTIONAL
while I wanted to call my HeuristicCallback on Integer nodes. Please note that on Integer Nodes, I do successfully add Lazy Constraints
. So my concern is having both Heuristic Callback AND Lazy Constraints called on integer nodes. Is there something I am missing?
Please note that I use both Heuristic Callbacks and Lazy Constraints from this Julia Doc page.
EDIT: Thanks to @mattmilten's answer, I am editing my post. I then tried to put the Heuristic Callbacks inside the Lazy Constraints Callback as you can not have two callbacks in JuMP.
function call_back_benders(cb_data)
ŷ = [Bool(round(callback_value(cb_data, y[i,i]))) for i in 1:n]
start_time_sp = time()
for i in 1:n
for j in i+1:n
x̂[i,j] = Bool(round(callback_value(cb_data, x[i, j])))
end
end
ring_edges = create_ring_edges_lazy(cb_data, x, n)
is_valid = validate_solution_lazy(ring_edges, ŷ, n)
if !is_valid
nsubtour_cons = create_subtour_constraint_lazy(m, cb_data, x, y, n, ring_edges, nsubtour_cons)
else
λ0 = callback_value(cb_data, λ)
start_time_sp = time()
if pars.sp_solve == "poly"
λ_val, α, γ, β, δ = sp_optimize_poly(ŷ, x̂, V, tildeV, rp, s, pars.sp_solve)
else
λ_val, α, γ, β, δ = sp_optimize_ilp_dual(ŷ, x̂, V, tildeV, rp, s, pars.log_level, gurobi_env)
end
sp_time += time() - start_time_sp
if λ0 < λ_val
RHS = sum([2(1-y[i,i])α[i] for i in V]) +
sum([(x[mima(β[j][1],j)] + x[mima(β[j][2],j)] - 1)rp[mima(β[j][1],β[j][2])]
for j in tildeV if length(β[j]) > 0]) -
sum([y[j,j]γ[i,j] for i in V, j in V if i != j])
pars.log_level > 1 && @info "Optimality cut found"
nopt_cons += 1
con = @build_constraint(λ ≥ RHS)
MOI.submit(m, MOI.LazyConstraint(cb_data), con)
################ 2-opt
status = callback_node_status(cb_data, m)
if status != MOI.CALLBACK_NODE_STATUS_FRACTIONAL
@show status
end
if status == MOI.CALLBACK_NODE_STATUS_INTEGER
if sum(x̂) > 0
x′, y_sp, sp_cost = compute_sp_res(x̂, ŷ, V, n, tildeV, r, s)
hubs, master_cost = compute_master_res(x̂, ŷ, V, n, tildeV, o, r)
x_opt = run_two_opt_wiki(x̂, x′, hubs, sp_cost+master_cost, pars, n, r, rp, tildeV)[1]
for i in V
status = MOI.submit(
m, MOI.HeuristicSolution(cb_data), [x[i,j] for j in i+1:n], [x_opt[i,j] for j in i+1:n]
)
end
println("I submitted a heuristic solution, and the status was: ", status)
end
end
end
end
MOI.set(m, MOI.LazyConstraintCallback(), call_back_benders)
However, Julia tells me this is an invalid Callback Usage:
ERROR: InvalidCallbackUsage: Cannot submit MathOptInterface.HeuristicSolution{Gurobi.CallbackData}(Gurobi.CallbackData( sense : minimize
number of variables = 14952
number of linear constraints = 103
number of quadratic constraints = 0
number of sos constraints = 0
number of non-zero coeffs = 14952
number of non-zero qp objective terms = 0
number of non-zero qp constraint terms = 0
, Ptr{Nothing} @0x000055755c429d40, 4)) inside a
MathOptInterface.LazyConstraintCallback().
Does it mean I can not use solver-independent callback from JuMP?
Upvotes: 0
Views: 324
Reputation: 6706
In Gurobi, only one callback function can be passed to the solver. Inside this function, you can define multiple actions for the different scenarios by separating them via the where
argument, as demonstrated in this Python example:
def mycallback(model, where):
if where == GRB.Callback.PRESOLVE:
# Presolve callback
cdels = model.cbGet(GRB.Callback.PRE_COLDEL)
rdels = model.cbGet(GRB.Callback.PRE_ROWDEL)
if cdels or rdels:
print(f"{cdel} columns and {rdel} rows are removed")
elif where == GRB.Callback.SIMPLEX:
# Simplex callback
itcnt = model.cbGet(GRB.Callback.SPX_ITRCNT)
print(f"{itcnt} simplex iterations")
elif where == GRB.Callback.MIP:
# General MIP callback
pass
elif where == GRB.Callback.MIPSOL:
# MIP solution callback
pass
elif where == GRB.Callback.MIPNODE:
# MIP node callback
pass
This should also be possible with JuMP but you need to define the callbacks yourself and combine them into a single function. The JuMP documentation explicitly warns that only one callback can be defined using the set
function:
You can only set each callback once. Calling set twice will over-write the earlier callback. In addition, if you use a solver-independent callback, you cannot set a solver-dependent callback.
Upvotes: 1