# Objective bound of a model within the callback

**URL:** <https://discourse.julialang.org/t/objective-bound-of-a-model-within-the-callback/74941>\
**Category:** Optimization (Mathematical)\
**Created:** [January 20, 2022, 2:17pm UTC](https://discourse.julialang.org/t/objective-bound-of-a-model-within-the-callback/74941 "2022-01-20T14:17:04Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![ahmad88](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ahmad88/32/32437_2.png) [@ahmad88](https://discourse.julialang.org/u/ahmad88)\
**Post date:** [January 20, 2022, 2:17pm UTC](https://discourse.julialang.org/t/objective-bound-of-a-model-within-the-callback/74941/1 "2022-01-20T14:17:04Z")

</div>

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

```julia
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

```julia
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

```

---

<div class="post-metadata">

**Author:** ![dschermer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dschermer/32/32093_2.png) [@dschermer](https://discourse.julialang.org/u/dschermer)\
**Post date:** [January 20, 2022, 4:57pm UTC](https://discourse.julialang.org/t/objective-bound-of-a-model-within-the-callback/74941/2 "2022-01-20T16:57:47Z")

</div>

Hi, the following code works and follows what is shown here: [https://github.com/jump-dev/Gurobi.jl](https://github.com/jump-dev/Gurobi.jl). In particular, you can not access `ObjBoundC`in a callback. For more details refer to [https://www.gurobi.com/documentation/9.5/refman/cb\_codes.html#sec:CallbackCodes](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)](https://www.gurobi.com/documentation/9.5/refman/objboundc.html). If you need floating values similar to ObjBoundC, you might update the code and attempt to use values `SPX_OBJVAL` related to the relaxation.

```julia
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)

```

```julia
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

```

---

<div class="post-metadata">

**Author:** ![ahmad88](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ahmad88/32/32437_2.png) [@ahmad88](https://discourse.julialang.org/u/ahmad88)\
**Post date:** [January 21, 2022, 9:05am UTC](https://discourse.julialang.org/t/objective-bound-of-a-model-within-the-callback/74941/3 "2022-01-21T09:05:20Z")

</div>

> [@dschermer](#):
>
> `Base.RefValue{Float64}`

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

---

<div class="post-metadata">

**Author:** ![dschermer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dschermer/32/32093_2.png) [@dschermer](https://discourse.julialang.org/u/dschermer)\
**Post date:** [January 21, 2022, 9:22am UTC](https://discourse.julialang.org/t/objective-bound-of-a-model-within-the-callback/74941/4 "2022-01-21T09:22:08Z")

</div>

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