Objective bound of a model within the callback

I have a model where I try to add cuts to it within the callback, I am trying to access the objective bound of the model but it is not possible in the callback with the following error message

Error evaluating cec-procedure.jl
LoadError: Gurobi Error 10005: Unable to retrieve attribute 'ObjBoundC'

There is any way to get the objective value and the objective bound in each step in the callback?
here is my full implementation

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)
        ## 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
            y_val = callback_value.(Ref(cb_data), y)
            x_val = callback_value.(Ref(cb_data), x)
            objbound=MOI.get(cec_model, Gurobi.ModelAttribute("ObjBoundC"))
            println(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.LazyConstraintCallback(), subtour_elimination)
        optimize!(cec_model)
end
@time begin
    cec_procedure()
    println("Total excution time:")
end

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_where in that function and query the primal values through load_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
2 Likes

Thank you so much, that is really awesome but my question now, how I can get the value of the object bound, I mean from this objbound = Base.RefValue{Float64}(15.0) I just want the 15

Replace @show(objbound) with @show(objbound[])
Output: objbound[] = 14.0

2 Likes