Hi, the following code works and follows what is shown here: https://github.com/jump-dev/Gurobi.jl. In particular, you can not access ObjBoundCin a callback. For more details refer to https://www.gurobi.com/documentation/9.5/refman/cb_codes.html#sec:CallbackCodes.
Key changes to your code:
- Use the solver-specific callback:
MOI.set(cec_model, Gurobi.CallbackFunction(), subtour_elimination) - As per the documentation, include
cb_wherein that function and query the primal values throughload_callback_variable_primal, - Follow the C API to query and update the objective bound.
Expected output is the following. Note that that MIP_OBJBND appears to account for the integrality in the objective, as one might also expect when looking at the documentation for ObjBound(C). If you need floating values similar to ObjBoundC, you might update the code and attempt to use values SPX_OBJVAL related to the relaxation.
Optimize a model with 373 rows, 158 columns and 1050 nonzeros
Model fingerprint: 0x2ae0d7ad
Variable types: 0 continuous, 158 integer (158 binary)
Coefficient statistics:
Matrix range [1e+00, 2e+00]
Objective range [1e+00, 1e+00]
Bounds range [0e+00, 0e+00]
RHS range [1e+00, 2e+00]
Presolve removed 49 rows and 9 columns
Presolve time: 0.00s
Presolved: 324 rows, 149 columns, 944 nonzeros
Variable types: 0 continuous, 149 integer (149 binary)
Root relaxation: objective 1.750000e+01, 235 iterations, 0.00 seconds (0.00 work units)
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
0 0 17.50000 0 61 - 17.50000 - - 0s
objbound = Base.RefValue{Float64}(15.0)
0 0 14.74359 0 69 - 14.74359 - - 0s
0 0 14.60287 0 64 - 14.60287 - - 0s
0 0 14.59184 0 69 - 14.59184 - - 0s
0 0 14.58965 0 64 - 14.58965 - - 0s
0 0 14.58673 0 65 - 14.58673 - - 0s
0 0 14.58673 0 63 - 14.58673 - - 0s
0 2 14.58664 0 63 - 14.58664 - - 0s
objbound = Base.RefValue{Float64}(14.0)
* 5 5 4 13.0000000 14.55823 12.0% 36.0 0s
objbound = Base.RefValue{Float64}(14.0)
objbound = Base.RefValue{Float64}(14.0)
objbound = Base.RefValue{Float64}(14.0)
using JuMP
import Gurobi
#import Random
function cec_procedure()
initial = 0
n = 32
s = 33
E = [(0,1),(3,17),(22,23),(6,7),(19,26),(16,9),(33,5),(33,21),
(2,3),(3,9),(22,15),(6,9),(19,13),(16,25),(33,6),(33,22),
(2,4),(3,13),(22,24),(6,26),(15,20),(16,26),(33,7),(33,23),
(2,5),(18,19),(22,16),(6,27),(15,24),(16,27),(33,8),(33,24),
(2,6),(18,16),(22,9),(7,9),(15,16),(28,13),(33,9),(33,25),
(2,7),(1,11),(22,25),(7,27),(15,28),(17,9),(33,10),(33,26),
(2,8),(1,19),(22,26),(29,19),(15,9),(9,26),(33,11),(33,27),
(2,9),(1,20),(22,27),(29,30),(15,26),(9,27),(33,12),(33,28),
(10,1),(14,6),(12,28),(29,13),(15,27),(25,13),(33,13),(33,29),
(10,11),(14,16),(12,26),(29,31),(20,16),(26,27),(33,14),(33,30),
(10,12),(14,9),(12,13),(23,17),(20,28),(26,13),(33,15),(33,31),
(10,13),(21,6),(5,29),(23,9),(20,9),(33,0),(33,16),(33,32),
(3,14),(21,15),(5,23),(19,20),(24,27),(33,1),(33,17),
(3,5),(21,20),(5,15),(19,16),(32,16),(33,2),(33,18),
(3,15),(21,9),(5,9),(19,25),(32,9),(33,3),(33,19),
(3,16),(22,6),(5,25),(19,30),(16,17),(33,4),(33,20)]
###################################################
cec_model = direct_model(Gurobi.Optimizer())
set_optimizer_attribute(cec_model, "Threads", 1)
set_optimizer_attribute(cec_model, "OptimalityTol", 1e-6)
@variable(cec_model,x[E],binary = true)
@variable(cec_model, y[v=initial:s],binary = true)
#constraint 2
for i = initial:n
@constraint(cec_model,(sum(x[(i,j)] for j in initial:s if (i,j) in E)
+sum(x[(j,i)] for j in initial:s if (j,i) in E)) == 2*y[i])
end
#constraint 3
@constraint(cec_model,sum(x[(s,i)] for i=initial:n)==2)
#constraint 5
for i = initial:n
for j = initial:s
if (j,i) in E
@constraint(cec_model,x[(j,i)] <= y[i])
@constraint(cec_model,x[(j,i)] <= y[j])
end
end
end
#constraint 6
for (i,j) in E
if i != s && j!= s
@constraint(cec_model,x[(i,j)] >= y[i]+y[j]-1)
end
end
@objective(cec_model, Max, sum(y[w] for w in initial:s)-1)
#####################################################
function subtour_elimination(cb_data, cb_where)
## We only check subtours for integer-feasible solutions
status = callback_node_status(cb_data, cec_model)
if status == MOI.CALLBACK_NODE_STATUS_INTEGER #|| status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL
Gurobi.load_callback_variable_primal(cb_data, cb_where)
y_val = callback_value.(Ref(cb_data), y)
x_val = callback_value.(Ref(cb_data), x)
objbound = Ref{Cdouble}()
Gurobi.GRBcbget(cb_data, cb_where, Gurobi.GRB_CB_MIPSOL_OBJBND, objbound)
@show(objbound)
# Write the current solution nodes into a list
T=[]
for ver in initial:s
if round(y_val[ver]) > 0
append!(T, ver)
end
end
cycle=[]
cycle = subtour(T,x_val)
# A subtour contains at least 3 nodes and at most (n-1)
for cy in range(1,length(cycle))
if !(isempty(cycle[cy])) && length(cycle[cy]) > 2
con = @build_constraint(sum(y[node] for node in cycle[cy]) <= length(cycle[cy]) - 1)
MOI.submit(cec_model, MOI.LazyConstraint(cb_data), con)
end
end
end
end
####################################################################
function subtour(vertices,x_values)
all_cycles=[]
while !(isempty(vertices))
cycle = collect(initial:s)
thiscycle = []
# Get the first item
nextv = first(vertices)
current = nextv
# Get the index of all nodes to which the current node is connected
index = findall(vertex -> (((current,vertex) in E)|| ((vertex,current) in E))
,vertices)
neighbours=[]
for ind in 1:length(index)
append!(neighbours,(vertices[index[ind]]))
end
prev = first(neighbours)
while true
push!(thiscycle, current)
posnext = findall(c -> (((current,c) in E && round(x_values[(current,c)]) > 0)
|| ((c,current) in E && round(x_values[(c,current)]) > 0)) && (c != prev),vertices)
temp = first(vertices[posnext])
prev = current
current = temp
if current == nextv
break
end
end
vertices = setdiff(vertices,thiscycle)
if length(thiscycle) > 2 && length(thiscycle) < length(cycle) && !(s in thiscycle)
cycle = thiscycle
push!(all_cycles,cycle)
else
cycle=[]
end
end
return all_cycles
end
MOI.set(cec_model, MOI.RawParameter("LazyConstraints"), 1)
MOI.set(cec_model, MOI.RawParameter("Heuristics"), 0)
MOI.set(cec_model, Gurobi.CallbackFunction(), subtour_elimination)
optimize!(cec_model)
end
@time begin
cec_procedure()
println("Total excution time:")
end