# I added 1 Million constraints to an LP unwittingly

**URL:** <https://discourse.julialang.org/t/i-added-1-million-constraints-to-an-lp-unwittingly/127668>\
**Category:** Optimization (Mathematical)\
**Tags:** question, jump, examples, gurobi, experience\
**Created:** [April 3, 2025, 11:12am UTC](https://discourse.julialang.org/t/i-added-1-million-constraints-to-an-lp-unwittingly/127668 "2025-04-03T11:12:09Z")\
**Posts on this page:** 1\
**Showing post:** 23

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [April 9, 2025, 3:22pm UTC](https://discourse.julialang.org/t/i-added-1-million-constraints-to-an-lp-unwittingly/127668/23 "2025-04-09T15:22:22Z")

</div>

I adapt your example to an MIQP problem  
Minimize `(x + 0.54)^2 - 1` over the interval `[-1, 1]` and `x` being `Int`.  
I intend to use Gurobi’s MIP callback-lazy cuts.  
But I have a novel idea—adding a Phase I before we conventionally start.  
The main question is:

- Do you think the `🍅` line is advantageous (e.g. has the potential to speed up the overall solving procedure)?

The secondary question is: see the last for-loop (`🟠`)

```julia
import JuMP, Gurobi; GRB_ENV = Gurobi.Env()
import JuMP.value as v
COT = 1e-6 # cut-off tolerance
a = 0.54 # data of the problem
f(x) = (x + a)^2 - 1
fˈ(x) = 2 * (x + a)
function set_user_cut(model, c) # via Gurobi's API
    JuMP.MOI.set(JuMP.backend(model), Gurobi.ConstraintAttribute("Lazy"), JuMP.index(c), -1)
end
function solve_to_optimality(model)
    JuMP.optimize!(model)
    JuMP.assert_is_solved_and_feasible(model; allow_local = false)
end
function my_callback_function(cb_data, cb_where::Cint)
    cb_where != Gurobi.GRB_CB_MIPSOL && return
    Gurobi.load_callback_variable_primal(cb_data, cb_where)
    xv = JuMP.callback_value(cb_data, x)
    yv = JuMP.callback_value(cb_data, y)
    @info "current trial at x = $xv, y = $yv"
    if yv < f(xv) - COT # ✅ generate cuts on-the-fly
        con = JuMP.@build_constraint(y >= f(xv) + fˈ(xv) * (x - xv))
        @info "push & add lazy constr" func=con.func set=con.set
        push!(from_MIP_cut_vec, con)
        JuMP.MOI.submit(model, JuMP.MOI.LazyConstraint(cb_data), con)
    end
end

# 🟣 Phase 1: Relax all "Int" constraints in the `model`
# Phase 1 is supposed to be fast, due to its continuous nature
model = JuMP.direct_model(Gurobi.Optimizer(GRB_ENV))
JuMP.set_silent(model)
JuMP.@variable(model, -1.98 <= x <= 1.98)
JuMP.@variable(model, y >= -7) # the lower bound -7 is an artificial one to avoid initial unboundedness
# we intend to emulate the following Quad constraint via linear cuts
# JuMP.@constraint(model, y >= f(x))
JuMP.@objective(model, Min, y)
solve_to_optimality(model)
from_LP_cut_vec = JuMP.ConstraintRef[] 
while true 
    if v(y) < f(v(x)) - COT # ✅ generate cuts on-the-fly
        push!(from_LP_cut_vec, JuMP.@constraint(model, y >= f(v(x)) + fˈ(v(x)) * (x - v(x))))
        solve_to_optimality(model)
    else
        @info "After adding $(length(from_LP_cut_vec)) cuts, (x, y) converges to ($(v(x)), $(v(y)))"
        break
    end
end

# 🔵 Phase 2: This is the `model` with `Int` constrs that we intend to solve
JuMP.unset_silent(model)
set_user_cut.(model, from_LP_cut_vec) # 🍅
JuMP.set_integer(x)
JuMP.set_attribute(model, "LazyConstraints", 1) # this setting is exclusively for the callback purpose
JuMP.MOI.set(model, Gurobi.CallbackFunction(), my_callback_function)
from_MIP_cut_vec = JuMP.ScalarConstraint[]
solve_to_optimality(model) # 🟡 From the Logging we observe that user cut is indeed adopted by Gurobi
JuMP.solution_summary(model; verbose = true) # This final result is correct
for i in from_MIP_cut_vec[1:2] # 🟠 [question] why are they the same ??
    @info i
end

```

- Update: why MOI has this behavior?

```julia
julia> a = from_MIP_cut_vec[1]
JuMP.ScalarConstraint{JuMP.AffExpr, MathOptInterface.GreaterThan{Float64}}(y + 0.9199999999999999 x, MathOptInterface.GreaterThan{Float64}(-1.7084))

julia> b = from_MIP_cut_vec[2]
JuMP.ScalarConstraint{JuMP.AffExpr, MathOptInterface.GreaterThan{Float64}}(y + 0.9199999999999999 x, MathOptInterface.GreaterThan{Float64}(-1.7084))

julia> a.func == b.func && a.set == b.set
true

julia> a == b
false

```

Therefore a related question is: how to remove this sort of redundancy, such that one cut occurs only once in the collection `from_MIP_cut_vec`?

---

_[View the full topic](https://discourse.julialang.org/t/i-added-1-million-constraints-to-an-lp-unwittingly/127668)._
