Inspired by this answer, I tried to create a small example using the JuMP direct_model
together with solver-specific callbacks. I adapted the knapsack example provided with JuMP to reject solutions using the most efficient item (in CPLEX, incumbent solutions may be rejected without the need to add a Lazy Constraint that invalidates the solution, so, I am using Lazy Constraints without the constraints themselves in the example). The relevant CPLEX documentations are about the context, and the method to reject candidate.
I am aware there is an effort to make some generic callbacks to work (documented here), but while they are a nice facility for some specific cases, I prefer something that works in all cases (no matter how obscure is the solver-specific behavior). And I am also aware of the existence of this answer, but I am not sure if it is the same thing. In the mentioned answer, the MOI.get
method is used, I am not sure if this is needed, and if this allows solver-specific behavior as the one I described for CPLEX.
The monster I assembled follows:
using JuMP, CPLEX, Test
const MOI = JuMP.MathOptInterface
function example_knapsack(; verbose = true)
n = 5
profit = [5, 3, 2, 7, 4]
weight = [2, 8, 4, 2, 5]
@test length(profit) == n
@test length(weight) == n
capacity = 10
cplex_backend = CPLEX.Optimizer()
model = direct_model(cplex_backend)
@test backend(model) === cplex_backend
@show typeof(cplex_backend)
MOI.set(cplex_backend, MOI.NumberOfThreads(), 1)
function my_callback(cb_context::CPLEX.CallbackContext, context_id::Clong)
if context_id == CPLEX.CPX_CALLBACKCONTEXT_CANDIDATE
primal_sol = Vector{Float64}(undef, n)
objective = Ref{Float64}(0.0)
CPLEX.cbcandidateispoint(cb_context) == 0 && return
CPLEX.cbgetcandidatepoint(
cb_context, primal_sol, Cint(0), Cint(n - 1), objective
)
# Just for testing the lazy constraints, here we reject
# any solution using the fist variable of greatest efficiency
# (which probably appears in the optimal solution of the model
# without the lazy cuts). No constraint is actually added,
# just the solution rejected.
e = profit ./ weight
max_e, idx = findmax(e)
#@show max_e
#@show idx
#@show primal_sol[idx]
if primal_sol[idx] â 0
println("accepted solution of value $(objective[])")
@show primal_sol
else
CPLEX.cbrejectcandidate(
cb_context, # one of the callback parameters
Cint(0), # rcnt: number of constraints to add
Cint(0), # nzcnt: number of nonzeros of added constraints
Cdouble[], # rhs: righthand sides for the constraints
Cchar[], # sense: sense for the constraints
Cint[], # rmatbeg: indexes of the two arrays below where
# each constraint start (the two arrays below may have a
# variable number of elements for each constraint)
Cint[], # rmatind: indexes of nonzero columns
Cdouble[] # rmatval: coefficients of nonzero columns
)
println("rejected solution of value $(objective[])")
@show primal_sol
end
else
error("Callback shold not be called from context_id $(context_id).")
end
end
CPLEX.cbsetfunc(cplex_backend.inner, CPLEX.CPX_CALLBACKCONTEXT_CANDIDATE, my_callback)
@variable(model, x[1:5], Bin)
# Objective: maximize profit
@objective(model, Max, profit' * x)
# Constraint: can carry all
@constraint(model, weight' * x <= capacity)
# Solve problem using MIP solver
JuMP.optimize!(model)
if verbose
println("Objective is: ", JuMP.objective_value(model))
println("Solution is:")
for i in 1:5
print("x[$i] = ", JuMP.value(x[i]))
println(", p[$i]/w[$i] = ", profit[i] / weight[i])
end
end
@test JuMP.termination_status(model) == MOI.OPTIMAL
@test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
#@test JuMP.objective_value(model) == 16.0
end
example_knapsack(verbose = true)
My concern is if this is the ârightâ way to do it. Should I use the MOI.get
methods whenever possible? (I have some trouble knowing where to look for their documentation.) I needed to disable multithread to get this working (otherwise I got segmentation faults), there are other pitfalls? What is the best way to link the variables created by JuMP with their variable index in CPLEX? (I need to be sure I am referring to the right variables inside the callback)