# Solver-specific callback for custom termination rule for LP Barrier using Gurobi and JuMP

**URL:** <https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391>\
**Category:** Optimization (Mathematical)\
**Tags:** question, gurobi\
**Created:** [September 29, 2023, 12:30pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391 "2023-09-29T12:30:20Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [September 29, 2023, 12:30pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/1 "2023-09-29T12:30:20Z")

</div>

Hi all,

I am implementing a Benders-like bundle method algorithm to solve a large-scale stochastic linear program. I want to use on-demand inexact cuts, i.e. I would like to solve the subproblems accurately only if they meet a certain criterion. To that end, I would like to provide the subproblem minimisation a threshold value corresponding to the upper bound of the overall algorithm. Solving the subproblem with Barrier, I can trace the dual bound and terminate the run early if it surpasses the threshold value for then I know that the subproblem will not yield a better result than the current upper bound.

I have implemented this using a Gurobi-specific callback. Please refer to the reproducible example below:

```julia
#%% -----------------------------
# Reproducible example custom stopping rule for LP barrier based on dual bound

using JuMP
using Gurobi
using JSON

data = JSON.parse("""
{
    "plants": {
        "Seattle": {"capacity": 350},
        "San-Diego": {"capacity": 600}
    },
    "markets": {
        "New-York": {"demand": 300},
        "Chicago": {"demand": 300},
        "Topeka": {"demand": 300}
    },
    "distances": {
        "Seattle => New-York": 2.5,
        "Seattle => Chicago": 1.7,
        "Seattle => Topeka": 1.8,
        "San-Diego => New-York": 2.5,
        "San-Diego => Chicago": 1.8,
        "San-Diego => Topeka": 1.4
    }
}
""")
P = keys(data["plants"])
M = keys(data["markets"])
distance(p::String, m::String) = data["distances"]["$(p) => $(m)"]
model = direct_model(Gurobi.Optimizer())
@variable(model, x[P, M] >= 0)
@constraint(model, [p in P], sum(x[p, :]) <= data["plants"][p]["capacity"])
@constraint(model, [m in M], sum(x[:, m]) >= data["markets"][m]["demand"])
@objective(model, Min, sum(distance(p, m) * x[p, m] for p in P, m in M));

# enforcing Barrier 
set_optimizer_attribute(model,"Method",2)
# No cross over
set_optimizer_attribute(model,"Crossover",0)

# Define dual bound threshold
thr = 1e3

# Define callback
c = Float64[]
function absolute_stopping_criterion(cb_data,cb_where)
    objbound =Ref{Cdouble}()
    Gurobi.GRBcbget(cb_data,cb_where,Gurobi.GRB_CB_BARRIER_DUALOBJ,objbound)
    objbound = objbound[]
    push!(c,objbound)
    if maximum(c) > thr
        GRBterminate(backend(model).inner)
    end
    return obj 
end

# Attach callback function to model
MOI.set(model,Gurobi.CallbackFunction(),absolute_stopping_criterion)

# Optimize model
optimize!(model)

# retrieve termination_status
termination_status(model)

# Obtain function value and primal solution
objective_value(model)
value.(model[:x])

```

While the algorithm terminates once the dual bound surpasses the threshold of 1000, as intended, the termination status is INTERRUPTED and I cannot access the (primal) objective value or the variable solutions of the prematurely terminated model. Is there a way around this, e.g. an alternative stopping command that treats the stopping criterion like the Gurobi Parameter _BarConvTol_. After all, using a IPM, I should be inside the convex hull defined by the problem constraints.

Thank you very much.

Best

Felix

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [September 29, 2023, 7:54pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/2 "2023-09-29T19:54:15Z")

</div>

cc @vasyfa works for Gurobi support and might be able to explain what’s going on and offer a suggestion.

But I assume that at the point when you terminate, Gurobi does not have a feasible primal-dual solution to return.

---

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [September 29, 2023, 8:06pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/3 "2023-09-29T20:06:15Z")

</div>

Hi Oscar,

Thank you very much for your answer!

Best

Felix

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [September 29, 2023, 8:24pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/4 "2023-09-29T20:24:35Z")

</div>

> [@FelixSchmidt](#):
>
> to solve a large-scale stochastic linear program. I want to use on-demand inexact cuts

You may be interested in [GitHub - odow/SDDP.jl: Stochastic Dual Dynamic Programming in Julia](https://github.com/odow/SDDP.jl), [https://sddp.dev](https://sddp.dev)

---

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [October 1, 2023, 9:35am UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/5 "2023-10-01T09:35:11Z")

</div>

I am! I have been experimenting with SDDP.jl for another project!

---

<div class="post-metadata">

**Author:** ![torressa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/torressa/32/202204_2.png) [@torressa](https://discourse.julialang.org/u/torressa)\
**Post date:** [October 2, 2023, 12:20pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/6 "2023-10-02T12:20:25Z")

</div>

Hi! I think the attributes are there just not accessible. e.g. you can run:

```julia
ret = GRBgetintattr(unsafe_backend(model), "SolCount", valueP)
println("solcount = $(valuePdouble[]) ret = $(ret)")
ret = GRBgetdblattrelement(unsafe_backend(model), "RC", 0, valuePdouble)
println("RC = $(valuePdouble[]) ret = $(ret)")
ret = GRBgetdblattrelement(unsafe_backend(model), "X", 0, valuePdouble)
println("X = $(valuePdouble[]) ret = $(ret)")

```

Gives:

```julia
solcount = 0 ret = 0
RC = 0.02203038263388657 ret = 0
X = 140.39787499441337 ret = 0

```

The issue lies in that the `SolCount` attribute is 0 which is used in the logic for [Gurobi.jl MOI\_wrapper](https://github.com/jump-dev/Gurobi.jl/blob/f3a85f27548d266cd4e9ae259be2d8655cae7861/src/MOI_wrapper/MOI_wrapper.jl#L2849C18-L2849C18). I will open an issue there.

---

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [October 2, 2023, 12:27pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/7 "2023-10-02T12:27:59Z")

</div>

Hi David,  
thanks for getting back to me! I see, so I reckon there is no way to do this at the moment?

Best

Felix

---

<div class="post-metadata">

**Author:** ![torressa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/torressa/32/202204_2.png) [@torressa](https://discourse.julialang.org/u/torressa)\
**Post date:** [October 2, 2023, 12:52pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/8 "2023-10-02T12:52:26Z")

</div>

There’s a hack:

```julia
# Hack variable values out
for p in P
    for m in M
        col = unsafe_backend(model).variable_info[x[p, m].index].column
        colc = Cint(col - 1)
        ret = GRBgetdblattrelement(unsafe_backend(model), "X", colc, valuePdouble)
        println("x = $(x[p,m]) has value $(valuePdouble[])")
    end
end

```

But perhaps someone more knowledgeable can suggest a nicer way.

---

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [October 2, 2023, 12:59pm UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/9 "2023-10-02T12:59:47Z")

</div>

Hi,

thanks so much! I will give this a try.

Best

Felix

---

<div class="post-metadata">

**Author:** ![torressa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/torressa/32/202204_2.png) [@torressa](https://discourse.julialang.org/u/torressa)\
**Post date:** [October 3, 2023, 8:48am UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/10 "2023-10-03T08:48:31Z")

</div>

Something to bear in mind (see [GitHub](https://github.com/jump-dev/Gurobi.jl/issues/519)) is that the solution might not be primal feasible. You can check some [quality attributes](https://www.gurobi.com/documentation/current/refman/quality_attributes.html) such as `MaxVio` or `ComplVio`.

---

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [October 4, 2023, 8:38am UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/11 "2023-10-04T08:38:27Z")

</div>

Thank you very much for looking into this. I will give the solution on Github a shot.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [October 4, 2023, 8:44am UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/12 "2023-10-04T08:44:50Z")

</div>

Does terminating early make a big difference to the run-time? If you don’t have a guarantee on the solution, I don’t really see the motivation for doing this.

---

<div class="post-metadata">

**Author:** ![FelixSchmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/felixschmidt/32/202177_2.png) [@FelixSchmidt](https://discourse.julialang.org/u/FelixSchmidt)\
**Post date:** [October 4, 2023, 8:57am UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/13 "2023-10-04T08:57:43Z")

</div>

It can potentially make a big difference, as each subproblem corresponds to solving an economic dispatch problem for a European energy system model with 8,760 time steps. Using on-demand accuracy oracles (e.g. [https://www.tandfonline.com/doi/abs/10.1080/10556788.2013.871282](https://www.tandfonline.com/doi/abs/10.1080/10556788.2013.871282)) can substantially speed up the algorithm especially if the subproblems are computationally cumbersome and I had hoped that this might be a simple way of implementing it. But I agree, it does not make much sense if the early stopping almost always returns infeasible solutions. I will run some tests.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [October 4, 2023, 9:10am UTC](https://discourse.julialang.org/t/solver-specific-callback-for-custom-termination-rule-for-lp-barrier-using-gurobi-and-jump/104391/14 "2023-10-04T09:10:49Z")

</div>

I guess this is really a feature request for Gurobi to implement [https://www.gurobi.com/documentation/current/refman/bestobjstop.html#parameter:BestObjStop](https://www.gurobi.com/documentation/current/refman/bestobjstop.html#parameter:BestObjStop) for LPs as well.
