# Gurobi12 reports obj\_value \< obj\_bound in a Min-Program?

**URL:** <https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849>\
**Category:** Optimization (Mathematical)\
**Tags:** jump, gurobi\
**Created:** [March 12, 2025, 8:09am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849 "2025-03-12T08:09:15Z")\
**Posts on this page:** 18\
**Page:** 1

<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:** [March 12, 2025, 8:09am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/1 "2025-03-12T08:09:15Z")

</div>

See the comments at the bottom of this code.

```julia
import JuMP, Gurobi
CR = JuMP.Model(() -> Gurobi.Optimizer())
JuMP.@variable(CR, x[1:3])
JuMP.set_lower_bound(x[1], exp(1)); JuMP.set_upper_bound(x[1], exp(2))
JuMP.set_lower_bound(x[3], 1e-6)
JuMP.@objective(CR, Min, -x[1] * x[2] - log(x[3]) + x[2]^2/4)
JuMP.optimize!(CR) # see the following Gurobi's log
# Solution count 1: -11.993
# Model is unbounded
# Best objective -1.199295535608e+01, best bound -, gap -
JuMP.termination_status(CR) # DUAL_INFEASIBLE
JuMP.objective_value(CR) # -11.992955356078266
JuMP.objective_bound(CR) # 1.0e100
# This relation is illogical in a Min-Program
# It should always holds that obj_value >= obj_bound ???
# I think in this case the outcome of `JuMP.objective_bound(CR)` should be something like -1.0e100

```

---

<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:** [March 12, 2025, 8:23am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/2 "2025-03-12T08:23:16Z")

</div>

Your problem is unbounded.

`primal_status(model)` is `NO_SOLUTION`, thus, calling `objective_value` and `objective_bound` is undefined behavior.

Please remember to \_always\_use `is_solved_and_feasible`, or check the termination status _and_ the primal status before querying a primal solution.

```Julia
julia> solution_summary(model)
* Solver : Gurobi

* Status
  Result count : 1
  Termination status : DUAL_INFEASIBLE
  Message from the solver:
  "Model was proven to be unbounded. Important note: an unbounded status indicates the presence of an unbounded ray that allows the objective to improve without limit. It says nothing about whether the model has a feasible solution. If you require information on feasibility, you should set the objective to zero and reoptimize."

* Candidate solution (result #1)
  Primal status : NO_SOLUTION
  Dual status : NO_SOLUTION
  Objective value : -1.19930e+01
  Objective bound : 1.00000e+100
  Dual objective value : 1.00000e+100

```

---

<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:** [March 12, 2025, 8:33am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/3 "2025-03-12T08:33:13Z")

</div>

Ok I see.  
Now here is a new problem.

```julia
import JuMP
import Gurobi
GRB_ENV = Gurobi.Env()
CR = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
JuMP.@variable(CR, x)
JuMP.@objective(CR, Min, (x - 1.) * x * (x + 1.))
JuMP.optimize!(CR) # see the continual logging
# here you hit `ctrl + C`
# Then you query
JuMP.termination_status(CR) # it shows OPTIMIZE_NOT_CALLED
# if I recall correctly, this should be INTERRUPTED ???

```

I’ve only encountered these in my life

```julia

Enum MathOptInterface.TerminationStatusCode:
OPTIMIZE_NOT_CALLED = 0 # exclusively only occurs when you forget to `optimize!(model)`
OPTIMAL = 1
INFEASIBLE = 2
DUAL_INFEASIBLE = 3
LOCALLY_SOLVED = 4
INFEASIBLE_OR_UNBOUNDED = 6
TIME_LIMIT = 12
SLOW_PROGRESS = 19
NUMERICAL_ERROR = 20
INTERRUPTED = 23 # happens when you press `ctrl + C` to manually stop the solver, but you could fetch solutions

```

---

<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:** [March 12, 2025, 8:37am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/4 "2025-03-12T08:37:29Z")

</div>

I mean, if I recall correctly, if I interrupt Gurobi through my keyboard.  
I could query the current solution like `JuMP.value(x)`.  
But now it only gives me the error.  
This is not what it used to be!

---

<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:** [March 12, 2025, 8:46am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/5 "2025-03-12T08:46:09Z")

</div>

As a comparison

```julia

import JuMP
import Gurobi
GRB_ENV = Gurobi.Env()
CR = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
JuMP.set_attribute(CR, "TIME_LIMIT", 4.)
JuMP.@variable(CR, x)
JuMP.@objective(CR, Min, (x - 1.) * x * (x + 1.))
JuMP.optimize!(CR) # see the continual logging
# then it terminates
@assert JuMP.termination_status(CR) == JuMP.TIME_LIMIT
x = JuMP.value(x) # -302.7000000000001
ub = JuMP.objective_value(CR) # -2.773527798300003e7
lb = JuMP.objective_bound(CR) # -5.36870912e14
lb < ub && println("This is sane 😄")
# Comment: This is the correct behavior since I can query the status (even before Gurobi returns to Julia)

```

---

<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:** [March 12, 2025, 9:03am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/6 "2025-03-12T09:03:16Z")

</div>

I can reproduce the CTRL+C thing. It may be a change in Gurobi 12 that we need to fix in Gurobi.jl. Open an issue

---

<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:** [March 12, 2025, 9:04am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/7 "2025-03-12T09:04:23Z")

</div>

> [@WalterMadelim](#):
>
> As a comparison  
> @assert JuMP.termination\_status(CR) == JuMP.TIME\_LIMIT  
> x = JuMP.value(x) # -302.7000000000001  
> ub = JuMP.objective\_value(CR) # -2.773527798300003e7  
> lb = JuMP.objective\_bound(CR) # -5.36870912e14

I’ll say again in bold: **Please remember to _always_ use `is_solved_and_feasible` , or check the termination status _and_ the primal status before querying a primal solution.** It is NOT sufficient to check only `termination_status`, although it worked in this case.

---

<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:** [March 12, 2025, 9:14am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/8 "2025-03-12T09:14:22Z")

</div>

Is this problem exclusive to Gurobi?  
It seems that this happens with Ipopt too.  
“this” means if you manually interrupt the solver, then the `termination_status` is not `JuMP.INTERRUPTED` but see below:

```julia
julia> JuMP.optimize!(CR)

******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit https://github.com/coin-or/Ipopt
******************************************************************************

This is Ipopt version 3.14.14, running with linear solver MUMPS 5.6.2.

Number of nonzeros in equality constraint Jacobian...: 0
Number of nonzeros in inequality constraint Jacobian.: 0
Number of nonzeros in Lagrangian Hessian.............: 1

ERROR: InterruptException:
Stacktrace:
  [1] eval_objective_gradient(model::Ipopt.Optimizer, grad::Vector{Float64}, x::Vector{Float64})
    @ Ipopt K:\judepot1113\packages\Ipopt\KZuET\src\MOI_wrapper.jl:872
  [2] (::Ipopt.var"#eval_grad_f_cb#4"{Ipopt.Optimizer})(x::Vector{Float64}, grad_f::Vector{Float64})
    @ Ipopt K:\judepot1113\packages\Ipopt\KZuET\src\MOI_wrapper.jl:981
  [3] _Eval_Grad_F_CB(n::Int32, x_ptr::Ptr{Float64}, ::Int32, grad_f::Ptr{Float64}, user_data::Ptr{Nothing})
    @ Ipopt K:\judepot1113\packages\Ipopt\KZuET\src\C_wrapper.jl:56
  [4] IpoptSolve
    @ K:\judepot1113\packages\Ipopt\KZuET\src\C_wrapper.jl:399 [inlined]
  [5] optimize!(model::Ipopt.Optimizer)
    @ Ipopt K:\judepot1113\packages\Ipopt\KZuET\src\MOI_wrapper.jl:1131
  [6] optimize!
    @ K:\judepot1113\packages\MathOptInterface\TYq6d\src\Bridges\bridge_optimizer.jl:367 [inlined]
  [7] optimize!
    @ K:\judepot1113\packages\MathOptInterface\TYq6d\src\MathOptInterface.jl:122 [inlined]
  [8] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{…})
    @ MathOptInterface.Utilities K:\judepot1113\packages\MathOptInterface\TYq6d\src\Utilities\cachingoptimizer.jl:327
  [9] optimize!(model::JuMP.Model; ignore_optimize_hook::Bool, _differentiation_backend::MathOptInterface.Nonlinear.SparseReverseMode, kwargs::@Kwargs{})
    @ JuMP K:\judepot1113\packages\JuMP\xlp0s\src\optimizer_interface.jl:595
 [10] optimize!(model::JuMP.Model)
    @ JuMP K:\judepot1113\packages\JuMP\xlp0s\src\optimizer_interface.jl:546
 [11] top-level scope
    @ REPL[7]:1
Some type information was truncated. Use `show(err)` to see complete types.

julia> JuMP.termination_status(CR)
OPTIMIZE_NOT_CALLED::TerminationStatusCode = 0

```

I think at least in literal meaning this is incorrect.  
JuMP should reports `JuMP.INTERRUPTED`.  
And `OPTIMIZE_NOT_CALLED` should be exclusively refer to the situation when I forget to write `JuMP.optimize!(model)`.  
I think this is my correct memory about JuMP in the past 2 years.

---

<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:** [March 12, 2025, 12:52pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/9 "2025-03-12T12:52:15Z")

</div>

I think `JuMP.primal_status` is somewhat unimportant, given the presence of `JuMP.has_values`. I’ve just written a rigorous procedure about this, please see [About JuMP.is\_solved\_and\_feasible](https://discourse.julialang.org/t/about-jump-is-solved-and-feasible/126868).

---

<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:** [March 12, 2025, 7:51pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/10 "2025-03-12T19:51:06Z")

</div>

In most cases, if you interrupt the solver mid-solve, it won’t have a solution to return to you. So in the Ipopt case, theres essentially no difference between reporting `INTERRUPTED` and `OPTIMIZE_NOT_CALLED`. But yes, we could do better at handling this.

I’ve opened issues for [Make CTRL+C work properly · Issue #460 · jump-dev/Ipopt.jl · GitHub](https://github.com/jump-dev/Ipopt.jl/issues/460) and [GitHub · Where software is built](https://github.com/jump-dev/Gurobi.jl/issues/620)

> I think `JuMP.primal_status` is somewhat unimportant, given the presence of `JuMP.has_values`

I opened a PR to remove `has_values` from the documentation [[docs] remove has\_values and has\_duals from documentation by odow · Pull Request #3961 · jump-dev/JuMP.jl · GitHub](https://github.com/jump-dev/JuMP.jl/pull/3961). Please don’t use it. `primal_status` is much more important.

---

<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:** [March 12, 2025, 11:07pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/11 "2025-03-12T23:07:41Z")

</div>

> [@odow](#):
>
> In most cases, if you interrupt the solver mid-solve, it won’t have a solution to return to you.

According to my experience, this is not the case, at least for Gurobi.  
The following 2 cases should be similar:

1. I set a `TimeLimit` at 4 seconds for Gurobi. Then After 4 seconds, after normality check (i.e. something like `assert_is_solved_and...`), I can get the current solution (which should be good, as Gurobi spend 4 seconds to reach).
2. I haven’t set attributes. I let Gurobi run 4 seconds. Then I use `Ctrl+C` to interrupt it. After normality check, I can also get the current solution.

This was my knowledge about Gurobi used in conjunction with JuMP. And I think this is the ideal behavior. (Gurobi 12’s behavior is disquieting for me😑)

---

<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:** [March 12, 2025, 11:15pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/12 "2025-03-12T23:15:16Z")

</div>

If you are solving an LP and stop with a time limit, it probably won’t have a feasible point to return. If you are solving a MIP, it depends whether Gurobi found a primal feasible point. It might find one in 1 second, or it might take 1000 years. You should not rely on the fact that Gurobi will have a solution when stopped early.

I’ve fixed the CTRL+C issue with Gurobi: [Improve handling of SIGINT interrupts by odow · Pull Request #621 · jump-dev/Gurobi.jl · GitHub](https://github.com/jump-dev/Gurobi.jl/pull/621). It may have been a change in Julia, not Gurobi. But regardless, I’m about to tag a new version.

---

<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:** [March 12, 2025, 11:20pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/13 "2025-03-12T23:20:36Z")

</div>

> [@odow](#):
>
> It might find one in 1 second

Actually I think this is 99% of the case. Since Gurobi tends to use heuristic first and it can indeed generate a feasible solution quickly at first, such that the primal bound can be updated soon.

---

<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:** [March 12, 2025, 11:44pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/14 "2025-03-12T23:44:50Z")

</div>

Here’s a “small” model with 1024 variables. Can you find a feasible solution?

```julia
julia> using JuMP, Gurobi, Downloads

julia> Downloads.download(
           "https://miplib.zib.de/WebData/instances/supportcase30.mps.gz",
           "/tmp/supportcase30.mps.gz",
       )
"/tmp/supportcase30.mps.gz"

julia> model = read_from_file("/tmp/supportcase30.mps.gz")
supportcase30
├ solver: none
├ objective_sense: MIN_SENSE
│ └ objective_function_type: AffExpr
├ num_variables: 1024
├ num_constraints: 4100
│ ├ AffExpr in MOI.EqualTo{Float64}: 4
│ ├ AffExpr in MOI.LessThan{Float64}: 1024
│ ├ VariableRef in MOI.GreaterThan{Float64}: 1024
│ ├ VariableRef in MOI.LessThan{Float64}: 1024
│ └ VariableRef in MOI.Integer: 1024
└ Names registered in the model: none

julia> set_optimizer(model, Gurobi.Optimizer)
Set parameter LicenseID to value 890341

julia> optimize!(model)
Gurobi Optimizer version 12.0.1 build v12.0.1rc0 (mac64[x86] - Darwin 24.1.0 24B83)

CPU model: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 1028 rows, 1024 columns and 12288 nonzeros
Model fingerprint: 0x93460ed6
Variable types: 0 continuous, 1024 integer (0 binary)
Coefficient statistics:
  Matrix range [1e+00, 1e+00]
  Objective range [0e+00, 0e+00]
  Bounds range [1e+00, 1e+00]
  RHS range [1e+00, 3e+01]
Presolve time: 0.01s
Presolved: 1028 rows, 1024 columns, 12288 nonzeros
Variable types: 0 continuous, 1024 integer (1024 binary)

Root relaxation: objective 0.000000e+00, 2883 iterations, 0.31 seconds (0.48 work units)

    Nodes | Current Node | Objective Bounds | Work
 Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time

     0 0 0.00000 0 473 - 0.00000 - - 1s
     0 0 0.00000 0 463 - 0.00000 - - 1s
     0 0 0.00000 0 466 - 0.00000 - - 1s
     0 0 0.00000 0 473 - 0.00000 - - 3s
     0 0 0.00000 0 473 - 0.00000 - - 4s
     0 2 0.00000 0 473 - 0.00000 - - 6s
    45 114 0.00000 7 431 - 0.00000 - 1395 11s
   240 266 infeasible 30 - 0.00000 - 754 18s
   455 312 infeasible 25 - 0.00000 - 610 23s
   697 340 infeasible 22 - 0.00000 - 581 28s
   897 366 0.00000 23 417 - 0.00000 - 616 33s
^C
Explored 1150 nodes (720861 simplex iterations) in 33.83 seconds (83.09 work units)
Thread count was 8 (of 8 available processors)

Solution count 0

Solve interrupted
Best objective -, best bound 0.000000000000e+00, gap -

User-callback calls 3772, time in user-callback 0.00 sec

```

---

<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:** [March 12, 2025, 11:52pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/15 "2025-03-12T23:52:39Z")

</div>

This case is spot on!

Well, from the Gurobi logging we know that it fails to offer a primal feasible solution.  
But importantly it can offer a valid dual bound, as shown in `best bound 0.000000000000e+00`.

In this case I’m expecting:  
After I hit `ctrl + C`, I call a normality check

```julia
@assert JuMP_objective_bound_works_properly(model)

```

If the above test is passed, I query

```julia
println( JuMP.objective_bound(model) )

```

I’m expecting that `0.0` occurs in julia REPL, as a valid dual bound.

---

<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:** [March 13, 2025, 12:07am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/16 "2025-03-13T00:07:59Z")

</div>

The latest release of Gurobi.jl returns

```julia
julia> solution_summary(model)
* Solver : Gurobi

* Status
  Result count : 0
  Termination status : INTERRUPTED
  Message from the solver:
  "Optimization was terminated by the user."

* Candidate solution (result #1)
  Primal status : NO_SOLUTION
  Dual status : NO_SOLUTION
  Objective bound : 0.00000e+00
  Relative gap : 1.00000e+100

* Work counters
  Solve time (sec) : 3.38293e+01
  Simplex iterations : 720861
  Barrier iterations : 0
  Node count : 1150

```

---

<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:** [March 13, 2025, 12:09am UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/17 "2025-03-13T00:09:52Z")

</div>

> [@odow](#):
>
> `Objective bound : 0.00000e+00`

Yes, this info is valuable.

---

<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:** [May 11, 2025, 3:00pm UTC](https://discourse.julialang.org/t/gurobi12-reports-obj-value-obj-bound-in-a-min-program/126849/18 "2025-05-11T15:00:58Z")

</div>

For future reference purposes, here I devise a “tough” disjointed bilinear problem parameterized by `N::Int`. (Such that we don’t need to download hard cases from miplib.) The problem has no constraint other than simple bounds on decisions. It’s style is very simple so that JuMP won’t feel strenuous to model it.

```julia
import JuMP, Ipopt, Gurobi
import Random, LinearAlgebra, SparseArrays
N = 40 # 18 seconds
N = 50 # 754 seconds
N = 80 # > 8500 seconds, already very tough
Random.seed!(1)
Q = 1. * SparseArrays.dropzeros(
    SparseArrays.spdiagm(
        [i => rand(-9:9, N - i) for i in 0:div(N, 2)]... # would be easier by increase `2`
    )
) # generate a random sparse quad-obj-coeff matrix
m = JuMP.Model();
JuMP.@variable(m, rand(-7:-3) <= y[1:N] <= rand(3:7));
JuMP.@variable(m, rand(-9:-5) <= x[1:N] <= rand(5:9));
JuMP.@objective(m, Min, LinearAlgebra.dot(x, Q, y));
JuMP.set_optimizer(m, Gurobi.Optimizer) # Gurobi will use a MIP method
JuMP.optimize!(m); JuMP.solution_summary(m)
JuMP.set_optimizer(m, Ipopt.Optimizer) # Ipopt uses NLP method
JuMP.optimize!(m); JuMP.solution_summary(m)

```

Another easier dense case that is directly derived from a simple BQP (boolean quadratic programming):

```julia
import LinearAlgebra.dot as dot
import JuMP, Gurobi
import Random
N = 20 # easier 
N = 80 # hard
Random.seed!(1)
Q = rand(-1:.0017:1, N, N)
m = JuMP.Model(Gurobi.Optimizer)
JuMP.@variable(m, x[1:N], Bin)
JuMP.@variable(m, y[1:N], Bin)
# the following 4 lines are classic BQP cuts
JuMP.@variable(m, z[1:N, 1:N] >= 0)
JuMP.@constraint(m, [j = 1:N], view(z, :, j) .<= x)
JuMP.@constraint(m, [i = 1:N], view(z, i, :) .<= y)
JuMP.@constraint(m, [i = 1:N, j = 1:N], z[i, j] >= x[i] + y[j] - 1)
JuMP.@objective(m, Min, dot(Q, z))
JuMP.optimize!(m)

```
