Gurobi gets stuck after "Loaded MIP start from previous solve"

I’m writing the Benders’ decomposition for an MIP and encountering a very weird behavior of Gurobi.

There is an MIP that I need to modify its obj and repeatedly re-solve (Gurobi can (according to its Logging) Loaded MIP start from previous solve with objective). Some obj coefficient might leads to a harder model (surely).

But I discover a model that once become very very slow to resolve. Therefore I set a TimeLimit and intercept its status which at that moment is questionable. Then I write it as an MPS file, and opened a new Julia REPL to directly solve that—it suddenly becomes very fast (at the supposed speed).

The code to generate the questionable model is here

import JuMP.value as ı
import JuMP, Gurobi; GRB_ENV = Gurobi.Env();
import LinearAlgebra.⋅ as ⋅
import Random

seed = 4599192817953253712 # may need to cherry-pick
Random.seed!(seed)

J = 9; # number of resources
W = rand(9:49, J); # the sole width type of each resource
o = rand(30:0.01:70, J); # occupied cost
u = 7 * rand(0.75:0.01:1.25, J)/30 .* W; # unit price
L = rand(6:17, J); # purchasing num limit
I = 10; # number of customer
w = rand(7:0.7:24, I); # width needed
S = 2; # number of scenes
d = rand(6:35, I, S); # number needed

function Min_s_prim_model(s) # evaluate the 2nd stage exactly ⚠️ Feasible Region is NOT fixed
    Min_s = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
    JuMP.@variable(Min_s, m2[1:J]) # JuMP.fix.(m2, m) # 🟡 [Jit] Linking 
    JuMP.@variable(Min_s, 0 ≤ n[1:I, 1:J], Int)
    JuMP.@constraint(Min_s, [i = 1:I], sum(view(n, i, :)) ≥ d[i, s])
    JuMP.@constraint(Min_s, [j = 1:J], m2[j]W[j] ≥ w ⋅ view(n, :, j))
    JuMP.@objective(Min_s, Min, 0/S)
    return Min_s
end; function Rhs_model_s(s) # 🟢 Feasible Region is fixed
    Rhs = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV));
    JuMP.@variable(Rhs, 0 ≤ b[1:J] ≤ 1, Int);
    JuMP.@variable(Rhs, 0 ≤ m[1:J], Int);
    JuMP.@constraint(Rhs, [j = 1:J], L[j]b[j] ≥ m[j]);
    JuMP.@objective(Rhs, Min, 0) # to be filled
    JuMP.@variable(Rhs, 0 ≤ n[1:I, 1:J], Int)
    JuMP.@constraint(Rhs, [i = 1:I], sum(view(n, i, :)) ≥ d[i, s])
    JuMP.@constraint(Rhs, [j = 1:J], m[j]W[j] ≥ w ⋅ view(n, :, j));
    ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### 
    # Rhs_1 = Rhs_model_vec[1];
    # μ = -0.1 * o
    # π = 0.1 * u
    # JuMP.set_objective_coefficient.(Rhs_1, Rhs_1[:b], μ)
    # JuMP.set_objective_coefficient.(Rhs_1, Rhs_1[:m], π)
    # JuMP.optimize!(Rhs_1); JuMP.assert_is_solved_and_feasible(Rhs_1, allow_local = false)
    # JuMP.objective_value(Rhs_1)
    ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### 
    return Rhs
end; function Rhs_surrogate_plus_s(s) # 🔵 θ-Cuts can be reused, whereas `varycon` needs to be modified
    Sur = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV));
    JuMP.@variable(Sur, ϕ ≤ 500.0); # ⚠️ initial upper bound need to tune
    JuMP.@objective(Sur, Max, ϕ)
    JuMP.@variable(Sur, θ); # This is the `Rhs_surrogate`, And the follows are `plus`
    JuMP.@variable(Sur, μ[1:J]);
    JuMP.@variable(Sur, π[1:J]);
    # θ-Cuts to be added, which reads `θ ≤ b ⋅ μ + m ⋅ π` in which (b, m) are vertices of the 🟢 Feasible Region of Rhs_model_s(s)
    JuMP.@constraint(Sur, varycon, ϕ - θ ≤ false); # TBD
    return Sur
end

prim_model_vec = [Min_s_prim_model(s) for s = 1:S]; # ✅ evaluate the 2nd stage ObjVals
Rhs_model_vec = [Rhs_model_s(s) for s = 1:S]; # ✅ evaluate the RHS of Lag cut
Sur_model_vec = [Rhs_surrogate_plus_s(s) for s = 1:S]; # ✅ used to generate trial coefficients of Lag cut
JuMP.set_silent.(prim_model_vec); JuMP.set_silent.(Rhs_model_vec); JuMP.set_silent.(Sur_model_vec); 
Mst = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); # ✅ used to generate trial 1st-stage solutions
JuMP.@variable(Mst, 0 ≤ b[1:J] ≤ 1, Int);
JuMP.@variable(Mst, 0 ≤ m[1:J], Int);
JuMP.@constraint(Mst, [j = 1:J], L[j]b[j] ≥ m[j]);
JuMP.@objective(Mst, Min, o ⋅ b + u ⋅ m);
JuMP.set_silent.(Mst); 

JuMP.unset_silent(Rhs_model_vec[1]) # This might be hard
JuMP.set_attribute(Rhs_model_vec[1], "TimeLimit", 60)
suspect = nothing

JuMP.optimize!(Mst); JuMP.assert_is_solved_and_feasible(Mst; allow_local = false)
B = ı.(b); # to be globally ✂️ off
M = ı.(m); # to be globally ✂️ off
while true # iteratively find a violating cut for the Master problem
    infeasible_block = 0
    for s = 1:S
        Min_s = prim_model_vec[s]; JuMP.fix.(Min_s[:m2], M);  
        JuMP.optimize!(Min_s); termination = JuMP.termination_status(Min_s)
        if termination == JuMP.INFEASIBLE
            infeasible_block = s
            break # [early break]
        elseif termination != JuMP.OPTIMAL
            error("unknown termination status")
        end
    end
    s = infeasible_block;
    s == 0 && error("We have now derived a 1st-stage FEASIBLE solution!")
    Sur = Sur_model_vec[s]; varycon = Sur[:varycon];
    θ, μ, π = Sur[:θ], Sur[:μ], Sur[:π];
    JuMP.set_normalized_coefficient.(varycon, μ, B);
    JuMP.set_normalized_coefficient.(varycon, π, M);
    JuMP.optimize!(Sur); JuMP.assert_is_solved_and_feasible(Sur; allow_local = false); ub = JuMP.objective_bound(Sur)
    Μ, Π = ı.(μ), ı.(π); # to be locally ✂️ off
    Θ = ı(θ);  # [🪜Upper] to be locally ✂️ off
    rhs = NaN; # [🪜Lower] pre-allocate
    while true # try generate the coefficient of Lag cut until success
        Rhs_s = Rhs_model_vec[s]
        local b, m = Rhs_s[:b], Rhs_s[:m]
        JuMP.set_objective_coefficient.(Rhs_s, b, Μ)
        JuMP.set_objective_coefficient.(Rhs_s, m, Π)
        JuMP.optimize!(Rhs_s); # JuMP.assert_is_solved_and_feasible(Rhs_s, allow_local = false)
        if JuMP.termination_status(Rhs_s) == JuMP.TIME_LIMIT
            suspect = Rhs_s
            return # 🍅🍅🍅
        end
        rhs = JuMP.objective_value(Rhs_s)
        lb = rhs - Μ ⋅ B - Π ⋅ M
        if lb > 4e-3 # 🟣 global COT
            @info "Find a cut for master with vio = $lb"
            break # go back to add cut for the outer Master
        end
        local_B, local_M = ı.(b), ı.(m) # vertices
        vertical_distance = Θ - (local_B ⋅ Μ + local_M ⋅ Π) # 🪜 having this since you approximate `Rhs_model_s`
        if vertical_distance > 8e-4 # 🟣 local COT
            JuMP.@constraint(Sur, θ ≤ local_B ⋅ μ + local_M ⋅ π)
        else
            error("weird 568435686586442")
        end
        JuMP.optimize!(Sur); JuMP.assert_is_solved_and_feasible(Sur; allow_local = false); ub = JuMP.objective_bound(Sur)
        Μ, Π = ı.(μ), ı.(π); # to be locally ✂️ off
        Θ = ı(θ); # [🪜Upper] to be locally ✂️ off
    end
    JuMP.@constraint(Mst, Μ ⋅ b + Π ⋅ m ≥ rhs)
    JuMP.optimize!(Mst); JuMP.assert_is_solved_and_feasible(Mst; allow_local = false)
    M = ı.(m); # to be globally ✂️ off
    B = ı.(b); # to be globally ✂️ off
end

Gurobi.GRBwrite(JuMP.unsafe_backend(suspect), "data/suspect.mps")

On my machine it branches enormously (If I don’t set a TIME_LIMIT, it will branch more and non-stop in a reasonable time):

Gurobi Optimizer version 12.0.2 build v12.0.2rc0 (win64 - Windows 11.0 (26100.2))

CPU model: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Non-default parameters:
TimeLimit  60

Optimize a model with 28 rows, 108 columns and 207 nonzeros
Model fingerprint: 0x8bbf69b7
Variable types: 0 continuous, 108 integer (0 binary)
Coefficient statistics:
  Matrix range     [1e+00, 5e+01]
  Objective range  [2e+01, 3e+04]
  Bounds range     [1e+00, 1e+00]
  RHS range        [6e+00, 4e+01]

Loaded MIP start from previous solve with objective -1.36424e-12

Presolve removed 7 rows and 7 columns
Presolve time: 0.00s
Presolved: 21 rows, 101 columns, 193 nonzeros
Variable types: 0 continuous, 101 integer (2 binary)

Root relaxation: objective -3.505153e+02, 53 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 -350.51533    0   11   -0.00000 -350.51533      -     -    0s
H    0     0                     -51.7171719 -350.51533   578%     -    0s
     0     0 -339.13976    0   20  -51.71717 -339.13976   556%     -    0s
H    0     0                     -53.4817681 -338.94230   534%     -    0s
     0     0 -327.61043    0   20  -53.48177 -327.61043   513%     -    0s
     0     0 -327.61043    0   20  -53.48177 -327.61043   513%     -    0s
     0     0 -327.61043    0   19  -53.48177 -327.61043   513%     -    0s
     0     0 -327.61043    0   15  -53.48177 -327.61043   513%     -    0s
     0     2 -327.57472    0   13  -53.48177 -327.57472   512%     -    0s
H  136   322                    -199.4028229 -327.57472  64.3%   3.0    0s
H  264   322                    -226.5814028 -327.57472  44.6%   3.2    0s
H 1138   806                    -300.0734385 -327.57472  9.16%   2.9    0s
H 1578   442                    -302.8250805 -326.42894  7.79%   2.9    0s
H33784  1717                    -302.8251207 -315.62847  4.23%   2.3    2s
H33791  1636                    -311.2578474 -315.62847  1.40%   2.3    2s
 66994  5793 infeasible   91      -311.25785 -315.62847  1.40%   2.4    5s
 184282  9413 infeasible  100      -311.25785 -315.62847  1.40%   2.1   10s
 390579 24443 -315.62847  107   14 -311.25785 -315.62847  1.40%   1.6   15s
 621856 32762 -315.62847  100    9 -311.25785 -315.62847  1.40%   1.4   20s
 820322 32676 infeasible   90      -311.25785 -315.62847  1.40%   1.3   25s
 1054675 32766 -315.62847   95    8 -311.25785 -315.62847  1.40%   1.2   30s
 1271044 32907 infeasible  100      -311.25785 -315.62847  1.40%   1.2   35s
 1483400 33327 -315.62847  115    9 -311.25785 -315.62847  1.40%   1.1   40s
 1691494 32747 -315.62847   96    7 -311.25785 -315.62847  1.40%   1.1   45s
 1876933 33645 infeasible   99      -311.25785 -315.62847  1.40%   1.1   50s
 2030884 33891 infeasible  117      -311.25785 -315.62847  1.40%   1.1   55s

Cutting planes:
  Gomory: 2
  MIR: 38
  StrongCG: 18
  Inf proof: 2

Explored 2206702 nodes (2405517 simplex iterations) in 60.01 seconds (23.12 work units)
Thread count was 8 (of 8 available processors)

Solution count 9: -311.258 -302.825 -302.825 ... -1.36424e-12

Time limit reached
Best objective -3.112578474459e+02, best bound -3.156284748041e+02, gap 1.4042%

User-callback calls 4426781, time in user-callback 1.42 sec

julia> 

julia> Gurobi.GRBwrite(JuMP.unsafe_backend(suspect), "data/suspect.mps")
0

After that, I open a new julia REPL and solve it directly, it is very quick.

julia> import Gurobi

julia> env = Gurobi.Env();
Set parameter Username
Set parameter LicenseID to value 2663413
Academic license - for non-commercial use only - expires 2026-05-11

julia> pModel = Ref{Ptr{Cvoid}}(C_NULL);

julia> Gurobi.GRBreadmodel(env, "data/suspect.mps", pModel)
Read MPS format model from file data/suspect.mps
Reading time = 0.00 seconds
: 28 rows, 108 columns, 207 nonzeros
0

julia> Gurobi.GRBoptimize(pModel[])
Gurobi Optimizer version 12.0.2 build v12.0.2rc0 (win64 - Windows 11.0 (26100.2))

CPU model: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 28 rows, 108 columns and 207 nonzeros
Model fingerprint: 0x8bbf69b7
Variable types: 0 continuous, 108 integer (0 binary)
Coefficient statistics:
  Matrix range     [1e+00, 5e+01]
  Objective range  [2e+01, 3e+04]
  Bounds range     [1e+00, 1e+00]
  RHS range        [6e+00, 4e+01]
Presolve removed 7 rows and 7 columns
Presolve time: 0.00s
Presolved: 21 rows, 101 columns, 193 nonzeros
Variable types: 0 continuous, 101 integer (2 binary)
Found heuristic solution: objective 6470.8950980

Root relaxation: objective -3.505153e+02, 53 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 -350.51533    0   11 6470.89510 -350.51533   105%     -    0s
H    0     0                    1617.7237745 -350.51533   122%     -    0s
H    0     0                     262.8039698 -350.51533   233%     -    0s
H    0     0                      46.5482749 -350.51533   853%     -    0s
H    0     0                     -53.4817681 -350.51533   555%     -    0s
     0     0 -339.13976    0   20  -53.48177 -339.13976   534%     -    0s
     0     0 -327.61043    0   20  -53.48177 -327.61043   513%     -    0s
H    0     0                     -99.3727799 -327.61043   230%     -    0s
     0     0 -327.61043    0   20  -99.37278 -327.61043   230%     -    0s
     0     0 -327.61043    0   19  -99.37278 -327.61043   230%     -    0s
H    0     0                    -179.8996033 -327.57472  82.1%     -    0s
     0     0 -327.57472    0   11 -179.89960 -327.57472  82.1%     -    0s
     0     0 -327.57472    0   15 -179.89960 -327.57472  82.1%     -    0s
     0     0 -327.57472    0   15 -179.89960 -327.57472  82.1%     -    0s
     0     0 -327.57472    0   17 -179.89960 -327.57472  82.1%     -    0s
H    0     0                    -226.5814028 -327.57472  44.6%     -    0s
     0     0 -327.57472    0   25 -226.58140 -327.57472  44.6%     -    0s
     0     0 -327.57472    0   17 -226.58140 -327.57472  44.6%     -    0s
H    0     0                    -251.5349570 -327.57472  30.2%     -    0s
     0     0 -327.57472    0   18 -251.53496 -327.57472  30.2%     -    0s
     0     0 -327.57472    0   18 -251.53496 -327.57472  30.2%     -    0s
     0     0 -327.57472    0   22 -251.53496 -327.57472  30.2%     -    0s
     0     0 -327.57472    0   22 -251.53496 -327.57472  30.2%     -    0s
     0     2 -327.57472    0   22 -251.53496 -327.57472  30.2%     -    0s
H   71   152                    -299.8524323 -327.57472  9.25%   1.4    0s
H 1687   920                    -303.1600430 -327.57472  8.05%   1.8    0s
H 2084   969                    -303.1600841 -324.45429  7.02%   1.8    0s
* 5703   486             104    -307.7487989 -315.62847  2.56%   2.2    0s
*16609  1257             128    -311.2578474 -311.25785  0.00%   2.6    1s

Cutting planes:
  Gomory: 5
  MIR: 15
  StrongCG: 11
  Inf proof: 1

Explored 17226 nodes (44703 simplex iterations) in 1.11 seconds (0.46 work units)
Thread count was 8 (of 8 available processors)

Solution count 10: -311.258 -307.749 -303.16 ... -53.4818
No other solutions better than -311.258

Optimal solution found (tolerance 1.00e-04)
Best objective -3.112578474459e+02, best bound -3.112578474459e+02, gap 0.0000%
0

Where is wrong?

My first post above itself is an issue—Gurobi’s re-optimize versus optimize-from-scratch.

In this post I reveal an easier issue with Gurobi—it branches a lot and fail to solve a small MIP instance in due time.

The code (independent from my first post) is

import JuMP, HiGHS
import Gurobi; GRB_ENV = Gurobi.Env();
import LinearAlgebra.⋅ as ⋅
import Random

begin
    seed = 4599192817953253712 # may need to cherry-pick
    Random.seed!(seed)
    J = 9; # number of resources
    W = rand(9:49, J); # the sole width type of each resource
    o = rand(30:0.01:70, J); # occupied cost
    u = 7 * rand(0.75:0.01:1.25, J)/30 .* W; # unit price
    L = rand(6:17, J); # purchasing num limit
    I = 10; # number of customer
    w = rand(7:0.7:24, I); # width needed
    S = 2; # number of scenes
    d = rand(6:35, I, S); # number needed
    Mu = [-65.10070711614117, 15.156225393559112, 0.0, -33369.33855998414, 0.0, 0.0, -32.74174265850863, -114.07026556634082, 256.3700412980869]
    Pi = [617.0164364724739, 306.42333246347755, 188.65466997510032, 462.20679034672725, 539.2412554045152, 539.2412554045152, 208.15789199261513, 316.285736343033, 376.20143138797914]
end

function solve_Rhs(s, Μ, Π, solver)
    if solver == 0
        Rhs = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV));
    else
        Rhs = JuMP.Model(HiGHS.Optimizer);
    end
    JuMP.@variable(Rhs, 0 ≤ b[1:J] ≤ 1, Int);
    JuMP.@variable(Rhs, 0 ≤ m[1:J], Int);
    JuMP.@constraint(Rhs, [j = 1:J], L[j]b[j] ≥ m[j]);
    JuMP.@objective(Rhs, Min, Μ ⋅ b + Π ⋅ m)
    JuMP.@variable(Rhs, 0 ≤ n[1:I, 1:J], Int)
    JuMP.@constraint(Rhs, [i = 1:I], sum(view(n, i, :)) ≥ d[i, s])
    JuMP.@constraint(Rhs, [j = 1:J], m[j]W[j] ≥ w ⋅ view(n, :, j));
    # JuMP.set_attribute(Rhs, "TimeLimit", 7200)
    JuMP.optimize!(Rhs); JuMP.assert_is_solved_and_feasible(Rhs; allow_local = false)
end

t0 = time()
solve_Rhs(1, Mu, Pi, 1)
println("HiGHSˈtime = $(time() - t0)")

t0 = time()
solve_Rhs(1, Mu, Pi, 0)
println("Gurobiˈtime = $(time() - t0)")

On my machine, HiGHS solves this small MIP instance within 11 seconds, while Gurobi cannot solve it within 430 seconds. Here are some results from my machine.

Solving report
  Status            Optimal
  Primal bound      -311.257861234
  Dual bound        -311.257861234
  Gap               0% (tolerance: 0.01%)
  P-D integral      0.993091429458
  Solution status   feasible
                    -311.257861234 (objective)
                    0 (bound viol.)
                    0 (int. viol.)
                    0 (row viol.)
  Timing            9.22 (total)
                    0.00 (presolve)
                    0.00 (solve)
                    0.00 (postsolve)
  Max sub-MIP depth 6
  Nodes             43260
  Repair LPs        0 (0 feasible; 0 iterations)
  LP iterations     100123 (total)
                    742 (strong br.)
                    3653 (separation)
                    14974 (heuristics)

julia> println("HiGHSˈtime = $(time() - t0)")
HiGHSˈtime = 11.315999984741211


julia> solve_Rhs(1, Mu, Pi, 0)
Gurobi Optimizer version 12.0.2 build v12.0.2rc0 (win64 - Windows 11.0 (26100.2))

CPU model: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 28 rows, 108 columns and 207 nonzeros
Model fingerprint: 0xba8bd591
Variable types: 0 continuous, 108 integer (0 binary)
Coefficient statistics:
  Matrix range     [1e+00, 5e+01]
  Objective range  [2e+01, 3e+04]
  Bounds range     [1e+00, 1e+00]
  RHS range        [6e+00, 4e+01]
Presolve removed 7 rows and 7 columns
Presolve time: 0.00s
Presolved: 21 rows, 101 columns, 193 nonzeros
Variable types: 0 continuous, 101 integer (2 binary)
Found heuristic solution: objective 6470.8950649

Root relaxation: objective -3.505154e+02, 53 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 -350.51535    0   11 6470.89506 -350.51535   105%     -    0s
H    0     0                    1617.7237662 -350.51535   122%     -    0s
H    0     0                     262.8039454 -350.51535   233%     -    0s
H    0     0                      46.5482567 -350.51535   853%     -    0s
H    0     0                     -53.4817909 -350.51535   555%     -    0s
     0     0 -339.13977    0   20  -53.48179 -339.13977   534%     -    0s
     0     0 -327.61045    0   20  -53.48179 -327.61045   513%     -    0s
     0     0 -327.61045    0   20  -53.48179 -327.61045   513%     -    0s
     0     0 -327.61045    0   19  -53.48179 -327.61045   513%     -    0s
H    0     0                    -137.1660070 -327.57474   139%     -    0s
H    0     0                    -161.6096353 -327.57474   103%     -    0s
     0     2 -327.57474    0   12 -161.60964 -327.57474   103%     -    0s
H  111   172                    -199.4028449 -327.57474  64.3%   2.8    0s
H  171   501                    -276.4373100 -327.57474  18.5%   2.8    0s
H 1431   734                    -303.1600645 -327.57474  8.05%   2.7    0s
H 1440   688                    -303.1600726 -327.57474  8.05%   2.7    0s
* 3412   744             118    -307.7488075 -322.12803  4.67%   2.4    0s
* 6870   684              69    -311.2578612 -315.62849  1.40%   2.9    0s
 211849 10731 -315.62849   86    6 -311.25786 -315.62849  1.40%   1.5    5s
 494125 25734 infeasible   88      -311.25786 -315.62849  1.40%   1.4   10s
 759529 36273 -315.62849   97    5 -311.25786 -315.62849  1.40%   1.3   15s
...omit lines.....
 16985734 1127182 -315.62849   88    8 -311.25787 -315.62849  1.40%   1.4  480s
 17121754 1135574 -315.62849   97   11 -311.25787 -315.62849  1.40%   1.4  485s
 17247531 1143422 infeasible  116      -311.25787 -315.62849  1.40%   1.4  490s
...I have no more patience to wait due to the branch size....

This looks like a case of performance variability What is performance variability?

Solving the same model for three different seeds:

Explored 64892 nodes (162895 simplex iterations) in 2.05 seconds (0.94 work units)
Explored 89482 nodes (200879 simplex iterations) in 2.94 seconds (1.12 work units)
*I interrupted this* Explored 5586604 nodes (6007635 simplex iterations) in 87.60 seconds (33.64 work units)

This can be mitigated by parameter tuning. E.g. setting Presolve=2 gives:

Explored 15606 nodes (32777 simplex iterations) in 1.04 seconds (0.34 work units)
Explored 7848 nodes (22143 simplex iterations) in 0.46 seconds (0.21 work units)
Explored 32147 nodes (69106 simplex iterations) in 1.44 seconds (0.57 work units)
1 Like

Thanks for your opinion. Your info is helpful.

But in my these 2 cases (e.g. the more straightforward second post), the performance is ill acceptable (infinite branches, non-terminating).

Could you provide a JuMP code to set this?

This indeed works for my second post:

julia> println("Gurobiˈtime = $(time() - t0)")
Gurobiˈtime = 25.765000104904175

But it doesn’t in a general sense help in the algorithm written in my first post. and hence the following question.

Is an MIP like this very hard? Notice there is no Warning in the Logging, i.e. no numeric issues.

Optimize a model with 28 rows, 108 columns and 207 nonzeros
Model fingerprint: 0xb387f3d4
Variable types: 0 continuous, 108 integer (0 binary)
Coefficient statistics:
  Matrix range     [1e+00, 5e+01]
  Objective range  [1e+02, 4e+05]
  Bounds range     [1e+00, 1e+00]
  RHS range        [6e+00, 4e+01]
Presolve removed 6 rows and 6 columns
Presolve time: 0.00s
Presolved: 22 rows, 102 columns, 195 nonzeros
Variable types: 0 continuous, 102 integer (3 binary)

The logging for solving it is like this

  35801363 5038524 -1200.6027   95    8 -1150.0657 -1267.9358  10.2%   2.2 1260s

It’s unimaginable for me, until today. Congratulations to me for discovering a class of very hard MIP problems, which I think is instructive. They occurs along the iterations of my algorithm in my first post.

You can will have to run the model multiple times changing the Seed parameter and calling reset every time (I can’t find a JuMP function for this but you can always call the GRBreset C function from Gurobi.jl)

Presolve=2 was on my machine after a quick experiment, I’m sure better parameters exists. In general, this can happen regardless of the solver, that’s why you should set a termination criteria (MIPGap, TimeLimit).

2 Likes

I write it as

Rhs = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); # build it this way
# omit modeling...
JuMP.set_attribute(Rhs, "TimeLimit", 4) # run at most 4 seconds
while true # retry until OPTIMAL
    JuMP.optimize!(Rhs)
    termination = JuMP.termination_status(Rhs)
    if termination == JuMP.TIME_LIMIT
        JuMP.set_attribute(Rhs, "Seed", rand(0:typemax(Int32)))
        err_code = Gurobi.GRBreset(JuMP.unsafe_backend(Rhs), 0)
        err_code == 0 || error("Gurobi reset ERROR: $err_code")
    elseif termination == JuMP.OPTIMAL
        break
    else
        error("terminate with $termination")
    end
end

it works for my second post case.

credible. it seems also applies for HiGHS.

(This is quite an advanced usage…)

Here is a SmallHardModel having a 6s TimeLimit, fails to be solved to OPTIMAL within 50 consecutive attempts with different Gurobi’s seeds.

What’s your opinion?

MPS file of SmallHardModel
* Signature: 0x34eaa3c19661dfee
NAME 
ROWS
 N  OBJ
 G  R0      
 G  R1      
 G  R2      
 G  R3      
 G  R4      
 G  R5      
 G  R6      
 G  R7      
 G  R8      
 G  R9      
 G  R10     
 G  R11     
 G  R12     
 G  R13     
 G  R14     
 G  R15     
 G  R16     
 G  R17     
 G  R18     
 G  R19     
 G  R20     
 G  R21     
 G  R22     
 G  R23     
 G  R24     
 G  R25     
 G  R26     
 G  R27     
COLUMNS
    MARKER    'MARKER'                 'INTORG'
    b[1]      R0        7
    b[2]      R1        11
    b[3]      OBJ       1.5464237204312122e-02
    b[3]      R2        13
    b[4]      R3        16
    b[5]      R4        11
    b[6]      R5        11
    b[7]      OBJ       -1.3355453911799518e-02
    b[7]      R6        9
    b[8]      R7        15
    b[9]      R8        10
    m[1]      OBJ       1.8416499341389969e-01
    m[1]      R0        -1
    m[1]      R19       45
    m[2]      OBJ       0.0934883396545771
    m[2]      R1        -1
    m[2]      R20       23
    m[3]      OBJ       5.6530978622614157e-02
    m[3]      R2        -1
    m[3]      R21       14
    m[4]      OBJ       1.3686853490423276e-01
    m[4]      R3        -1
    m[4]      R22       34
    m[5]      OBJ       1.6307740329014969e-01
    m[5]      R4        -1
    m[5]      R23       40
    m[6]      OBJ       1.6272594593390910e-01
    m[6]      R5        -1
    m[6]      R24       40
    m[7]      OBJ       6.1154026233806148e-02
    m[7]      R6        -1
    m[7]      R25       15
    m[8]      R7        -1
    m[8]      R26       23
    m[9]      OBJ       1.1317008683069976e-01
    m[9]      R8        -1
    m[9]      R27       28
    n[1,1]    R9        1
    n[1,1]    R19       -8.4
    n[2,1]    R10       1
    n[2,1]    R19       -12.6
    n[3,1]    R11       1
    n[3,1]    R19       -12.6
    n[4,1]    R12       1
    n[4,1]    R19       -9.1
    n[5,1]    R13       1
    n[5,1]    R19       -17.5
    n[6,1]    R14       1
    n[6,1]    R19       -16.1
    n[7,1]    R15       1
    n[7,1]    R19       -15.4
    n[8,1]    R16       1
    n[8,1]    R19       -21.7
    n[9,1]    R17       1
    n[9,1]    R19       -13.3
    n[10,1]   R18       1
    n[10,1]   R19       -9.1
    n[1,2]    R9        1
    n[1,2]    R20       -8.4
    n[2,2]    R10       1
    n[2,2]    R20       -12.6
    n[3,2]    R11       1
    n[3,2]    R20       -12.6
    n[4,2]    R12       1
    n[4,2]    R20       -9.1
    n[5,2]    R13       1
    n[5,2]    R20       -17.5
    n[6,2]    R14       1
    n[6,2]    R20       -16.1
    n[7,2]    R15       1
    n[7,2]    R20       -15.4
    n[8,2]    R16       1
    n[8,2]    R20       -21.7
    n[9,2]    R17       1
    n[9,2]    R20       -13.3
    n[10,2]   R18       1
    n[10,2]   R20       -9.1
    n[1,3]    R9        1
    n[1,3]    R21       -8.4
    n[2,3]    R10       1
    n[2,3]    R21       -12.6
    n[3,3]    R11       1
    n[3,3]    R21       -12.6
    n[4,3]    R12       1
    n[4,3]    R21       -9.1
    n[5,3]    R13       1
    n[5,3]    R21       -17.5
    n[6,3]    R14       1
    n[6,3]    R21       -16.1
    n[7,3]    R15       1
    n[7,3]    R21       -15.4
    n[8,3]    R16       1
    n[8,3]    R21       -21.7
    n[9,3]    R17       1
    n[9,3]    R21       -13.3
    n[10,3]   R18       1
    n[10,3]   R21       -9.1
    n[1,4]    R9        1
    n[1,4]    R22       -8.4
    n[2,4]    R10       1
    n[2,4]    R22       -12.6
    n[3,4]    R11       1
    n[3,4]    R22       -12.6
    n[4,4]    R12       1
    n[4,4]    R22       -9.1
    n[5,4]    R13       1
    n[5,4]    R22       -17.5
    n[6,4]    R14       1
    n[6,4]    R22       -16.1
    n[7,4]    R15       1
    n[7,4]    R22       -15.4
    n[8,4]    R16       1
    n[8,4]    R22       -21.7
    n[9,4]    R17       1
    n[9,4]    R22       -13.3
    n[10,4]   R18       1
    n[10,4]   R22       -9.1
    n[1,5]    R9        1
    n[1,5]    R23       -8.4
    n[2,5]    R10       1
    n[2,5]    R23       -12.6
    n[3,5]    R11       1
    n[3,5]    R23       -12.6
    n[4,5]    R12       1
    n[4,5]    R23       -9.1
    n[5,5]    R13       1
    n[5,5]    R23       -17.5
    n[6,5]    R14       1
    n[6,5]    R23       -16.1
    n[7,5]    R15       1
    n[7,5]    R23       -15.4
    n[8,5]    R16       1
    n[8,5]    R23       -21.7
    n[9,5]    R17       1
    n[9,5]    R23       -13.3
    n[10,5]   R18       1
    n[10,5]   R23       -9.1
    n[1,6]    R9        1
    n[1,6]    R24       -8.4
    n[2,6]    R10       1
    n[2,6]    R24       -12.6
    n[3,6]    R11       1
    n[3,6]    R24       -12.6
    n[4,6]    R12       1
    n[4,6]    R24       -9.1
    n[5,6]    R13       1
    n[5,6]    R24       -17.5
    n[6,6]    R14       1
    n[6,6]    R24       -16.1
    n[7,6]    R15       1
    n[7,6]    R24       -15.4
    n[8,6]    R16       1
    n[8,6]    R24       -21.7
    n[9,6]    R17       1
    n[9,6]    R24       -13.3
    n[10,6]   R18       1
    n[10,6]   R24       -9.1
    n[1,7]    R9        1
    n[1,7]    R25       -8.4
    n[2,7]    R10       1
    n[2,7]    R25       -12.6
    n[3,7]    R11       1
    n[3,7]    R25       -12.6
    n[4,7]    R12       1
    n[4,7]    R25       -9.1
    n[5,7]    R13       1
    n[5,7]    R25       -17.5
    n[6,7]    R14       1
    n[6,7]    R25       -16.1
    n[7,7]    R15       1
    n[7,7]    R25       -15.4
    n[8,7]    R16       1
    n[8,7]    R25       -21.7
    n[9,7]    R17       1
    n[9,7]    R25       -13.3
    n[10,7]   R18       1
    n[10,7]   R25       -9.1
    n[1,8]    R9        1
    n[1,8]    R26       -8.4
    n[2,8]    R10       1
    n[2,8]    R26       -12.6
    n[3,8]    R11       1
    n[3,8]    R26       -12.6
    n[4,8]    R12       1
    n[4,8]    R26       -9.1
    n[5,8]    R13       1
    n[5,8]    R26       -17.5
    n[6,8]    R14       1
    n[6,8]    R26       -16.1
    n[7,8]    R15       1
    n[7,8]    R26       -15.4
    n[8,8]    R16       1
    n[8,8]    R26       -21.7
    n[9,8]    R17       1
    n[9,8]    R26       -13.3
    n[10,8]   R18       1
    n[10,8]   R26       -9.1
    n[1,9]    R9        1
    n[1,9]    R27       -8.4
    n[2,9]    R10       1
    n[2,9]    R27       -12.6
    n[3,9]    R11       1
    n[3,9]    R27       -12.6
    n[4,9]    R12       1
    n[4,9]    R27       -9.1
    n[5,9]    R13       1
    n[5,9]    R27       -17.5
    n[6,9]    R14       1
    n[6,9]    R27       -16.1
    n[7,9]    R15       1
    n[7,9]    R27       -15.4
    n[8,9]    R16       1
    n[8,9]    R27       -21.7
    n[9,9]    R17       1
    n[9,9]    R27       -13.3
    n[10,9]   R18       1
    n[10,9]   R27       -9.1
    MARKER    'MARKER'                 'INTEND'
RHS
    RHS1      R9        20
    RHS1      R10       16
    RHS1      R11       21
    RHS1      R12       22
    RHS1      R13       6
    RHS1      R14       8
    RHS1      R15       21
    RHS1      R16       19
    RHS1      R17       35
    RHS1      R18       20
BOUNDS
 UP BND1      b[1]      1
 UP BND1      b[2]      1
 UP BND1      b[3]      1
 UP BND1      b[4]      1
 UP BND1      b[5]      1
 UP BND1      b[6]      1
 UP BND1      b[7]      1
 UP BND1      b[8]      1
 UP BND1      b[9]      1
 LI BND1      m[1]      0
 LI BND1      m[2]      0
 LI BND1      m[3]      0
 LI BND1      m[4]      0
 LI BND1      m[5]      0
 LI BND1      m[6]      0
 LI BND1      m[7]      0
 LI BND1      m[8]      0
 LI BND1      m[9]      0
 LI BND1      n[1,1]    0
 LI BND1      n[2,1]    0
 LI BND1      n[3,1]    0
 LI BND1      n[4,1]    0
 LI BND1      n[5,1]    0
 LI BND1      n[6,1]    0
 LI BND1      n[7,1]    0
 LI BND1      n[8,1]    0
 LI BND1      n[9,1]    0
 LI BND1      n[10,1]   0
 LI BND1      n[1,2]    0
 LI BND1      n[2,2]    0
 LI BND1      n[3,2]    0
 LI BND1      n[4,2]    0
 LI BND1      n[5,2]    0
 LI BND1      n[6,2]    0
 LI BND1      n[7,2]    0
 LI BND1      n[8,2]    0
 LI BND1      n[9,2]    0
 LI BND1      n[10,2]   0
 LI BND1      n[1,3]    0
 LI BND1      n[2,3]    0
 LI BND1      n[3,3]    0
 LI BND1      n[4,3]    0
 LI BND1      n[5,3]    0
 LI BND1      n[6,3]    0
 LI BND1      n[7,3]    0
 LI BND1      n[8,3]    0
 LI BND1      n[9,3]    0
 LI BND1      n[10,3]   0
 LI BND1      n[1,4]    0
 LI BND1      n[2,4]    0
 LI BND1      n[3,4]    0
 LI BND1      n[4,4]    0
 LI BND1      n[5,4]    0
 LI BND1      n[6,4]    0
 LI BND1      n[7,4]    0
 LI BND1      n[8,4]    0
 LI BND1      n[9,4]    0
 LI BND1      n[10,4]   0
 LI BND1      n[1,5]    0
 LI BND1      n[2,5]    0
 LI BND1      n[3,5]    0
 LI BND1      n[4,5]    0
 LI BND1      n[5,5]    0
 LI BND1      n[6,5]    0
 LI BND1      n[7,5]    0
 LI BND1      n[8,5]    0
 LI BND1      n[9,5]    0
 LI BND1      n[10,5]   0
 LI BND1      n[1,6]    0
 LI BND1      n[2,6]    0
 LI BND1      n[3,6]    0
 LI BND1      n[4,6]    0
 LI BND1      n[5,6]    0
 LI BND1      n[6,6]    0
 LI BND1      n[7,6]    0
 LI BND1      n[8,6]    0
 LI BND1      n[9,6]    0
 LI BND1      n[10,6]   0
 LI BND1      n[1,7]    0
 LI BND1      n[2,7]    0
 LI BND1      n[3,7]    0
 LI BND1      n[4,7]    0
 LI BND1      n[5,7]    0
 LI BND1      n[6,7]    0
 LI BND1      n[7,7]    0
 LI BND1      n[8,7]    0
 LI BND1      n[9,7]    0
 LI BND1      n[10,7]   0
 LI BND1      n[1,8]    0
 LI BND1      n[2,8]    0
 LI BND1      n[3,8]    0
 LI BND1      n[4,8]    0
 LI BND1      n[5,8]    0
 LI BND1      n[6,8]    0
 LI BND1      n[7,8]    0
 LI BND1      n[8,8]    0
 LI BND1      n[9,8]    0
 LI BND1      n[10,8]   0
 LI BND1      n[1,9]    0
 LI BND1      n[2,9]    0
 LI BND1      n[3,9]    0
 LI BND1      n[4,9]    0
 LI BND1      n[5,9]    0
 LI BND1      n[6,9]    0
 LI BND1      n[7,9]    0
 LI BND1      n[8,9]    0
 LI BND1      n[9,9]    0
 LI BND1      n[10,9]   0
ENDATA

On my machine it looks like

julia> Gurobi.GRBreadmodel(env, "data/SmallHardModel.mps", pModel)
Read MPS format model from file data/SmallHardModel.mps
Reading time = 0.00 seconds
: 28 rows, 108 columns, 207 nonzeros
0

julia> Gurobi.GRBoptimize(pModel[])
Gurobi Optimizer version 12.0.2 build v12.0.2rc0 (win64 - Windows 11.0 (26100.2))

CPU model: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 28 rows, 108 columns and 207 nonzeros
Model fingerprint: 0x7f6ed3f7
Variable types: 0 continuous, 108 integer (0 binary)
Coefficient statistics:
  Matrix range     [1e+00, 5e+01]
  Objective range  [1e-02, 2e-01]
  Bounds range     [1e+00, 1e+00]
  RHS range        [6e+00, 4e+01]
Presolve removed 8 rows and 9 columns
Presolve time: 0.00s
Presolved: 20 rows, 99 columns, 190 nonzeros
Variable types: 0 continuous, 99 integer (1 binary)
Found heuristic solution: objective 8.7316200

Root relaxation: objective 8.532225e+00, 39 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    8.53223    0   11    8.73162    8.53223  2.28%     -    0s
H    0     0                       8.7124249    8.53223  2.07%     -    0s
H    0     0                       8.6558940    8.53223  1.43%     -    0s
H    0     0                       8.6550018    8.53268  1.41%     -    0s
     0     0    8.53584    0   21    8.65500    8.53584  1.38%     -    0s
H    0     0                       8.5938478    8.53590  0.67%     -    0s
H   31    67                       8.5624172    8.53635  0.30%   3.5    0s
H 1774  1205                       8.5537813    8.53668  0.20%   2.4    0s
H 1775  1146                       8.5509696    8.53779  0.15%   2.4    0s
 13068305 147141    8.54043  136    8    8.54218    8.54043  0.02%   1.6  325s
omit lines...

In this solve, it can only converges to 0.02% after 2s, but the gap never reduces further. Will you recommend me to broaden the MIPGap to above 0.02%?

Well, thought it doesn’t look decent. I do succeed finishing my Benders’ algorithm algorithm :sweat_smile:.

Code of my algorithm
import JuMP.value as ı
import LinearAlgebra.⋅ as ⋅
import Random, JuMP, Gurobi; GRB_ENV = Gurobi.Env();

seed = 4599192817953253712 # may need to cherry-pick
Random.seed!(seed)
J = 9; # number of resources
W = rand(9:49, J); # the sole width type of each resource
o = rand(30:0.01:70, J); # occupied cost
u = 7 * rand(0.75:0.01:1.25, J)/30 .* W; # unit price
L = rand(6:17, J); # purchasing num limit
I = 10; # number of customer
w = rand(7:0.7:24, I); # width needed
S = 2; # number of scenes
d = rand(6:35, I, S); # number needed

if false # This is the original MIP. I attempt to use Benders Decomposition to solve it
    Xtn = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); # extensive formulation
    # Xtn = JuMP.Model(HiGHS.Optimizer)
    JuMP.@variable(Xtn, 0 ≤ b[1:J] ≤ 1, Int); # use this site or not
    JuMP.@variable(Xtn, 0 ≤ m[1:J], Int); # order an amount
    JuMP.@constraint(Xtn, [j = 1:J], L[j]b[j] ≥ m[j]); # source num is limited
    JuMP.@expression(Xtn, Obj_1, o ⋅ b + u ⋅ m);
    # each s indexes a block
    JuMP.@variable(Xtn, 0 ≤ m2[1:J, 1:S]);
    JuMP.@variable(Xtn, 0 ≤ n[1:I, 1:J, 1:S], Int);
    JuMP.@constraint(Xtn, [i = 1:I, s = 1:S], sum(view(n, i, :, s)) ≥ d[i, s]);
    JuMP.@constraint(Xtn, [j = 1:J, s = 1:S], m2[j, s]W[j] ≥ w ⋅ view(n, :, j, s)); 
    JuMP.@constraint(Xtn, [j = 1:J, s = 1:S], m2[j, s] == m[j]); # 🟡 Linking
    # temporarily don't consider obj from 2nd stage
    JuMP.@objective(Xtn, Min, Obj_1 + sum(0/S for s = 1:S)) 
    JuMP.unset_integer.(b)
    JuMP.unset_integer.(m)
    JuMP.unset_integer.(n)
    JuMP.optimize!(Xtn); JuMP.assert_is_solved_and_feasible(Xtn; allow_local = false)
    LP_val = JuMP.objective_value(Xtn)
    JuMP.set_integer.(b)
    JuMP.set_integer.(m)
    JuMP.set_integer.(n)
    JuMP.optimize!(Xtn); JuMP.assert_is_solved_and_feasible(Xtn; allow_local = false)
    IP_val = JuMP.objective_value(Xtn);
    @info "$LP_val < $IP_val" # 949.2709333333335 < 986.3943333333333
    @info "optimal solution" B = JuMP.value.(b) M = JuMP.value.(m)
    # ┌ Info: optimal solution
    # │   B =
    # │    9-element Vector{Float64}:
    # │     1.0
    # │     1.0
    # │     0.0
    # │     1.0
    # │     1.0
    # │     1.0
    # │     1.0
    # │     1.0
    # │     1.0
    # │   M =
    # │    9-element Vector{Float64}:
    # │      7.0
    # │     11.0
    # │      0.0
    # │     16.0
    # │      8.0
    # │     11.0
    # │      9.0
    # │     15.0
    # └     10.0
end;

function Min_s_prim_model(s) # evaluate the 2nd-stage-scene-s exactly ⚠️ Feasible Region is NOT fixed
    Min_s = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
    JuMP.@variable(Min_s, m2[1:J]) # JuMP.fix.(m2, m) 🟡 [Jit] Linking 
    JuMP.@variable(Min_s, 0 ≤ n[1:I, 1:J], Int)
    JuMP.@constraint(Min_s, [i = 1:I], sum(view(n, i, :)) ≥ d[i, s])
    JuMP.@constraint(Min_s, [j = 1:J], m2[j]W[j] ≥ w ⋅ view(n, :, j))
    JuMP.@objective(Min_s, Min, 0/S)
    return Min_s
end; function Rhs_model(s) # 🟢 Feasible Region is fixed
    Rhs = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
    JuMP.set_attribute(Rhs, "TimeLimit", 6)
    JuMP.set_attribute(Rhs, "MIPGap", 1e-3)
    JuMP.@variable(Rhs, 0 ≤ b[1:J] ≤ 1, Int)
    JuMP.@variable(Rhs, 0 ≤ m[1:J], Int); JuMP.@constraint(Rhs, [j = 1:J], L[j]b[j] ≥ m[j])
    JuMP.@objective(Rhs, Min, 0) # TODO JuMP.set_objective_coefficient
    # For the single scene s
    JuMP.@variable(Rhs, 0 ≤ n[1:I, 1:J], Int)
    JuMP.@constraint(Rhs, [i = 1:I], sum(view(n, i, :)) ≥ d[i, s])
    JuMP.@constraint(Rhs, [j = 1:J], m[j]W[j] ≥ w ⋅ view(n, :, j))
    return Rhs
end; function Rhs_surrogate_plus_s(s)
    Sur = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV));
    JuMP.@variable(Sur, μ[1:J])
    JuMP.@variable(Sur, π[1:J])
    JuMP.@constraint(Sur, [true; μ; π] ∈ JuMP.MOI.NormOneCone(1+2J))
    JuMP.@variable(Sur, θ) # > Rhs(μ, π) initially
    JuMP.@objective(Sur, Max, θ) # need more 🟣 θ-Cuts (can be reused)
    JuMP.set_objective_coefficient.(Sur, μ, -falses(J)) # -B
    JuMP.set_objective_coefficient.(Sur, π, -falses(J)) # -M
    return Sur
end; macro ready_Rhs_and_Sur_code()
    esc(quote
        Sur_model_vec = [Rhs_surrogate_plus_s(s) for s = 1:S]; # ✅ used to generate trial coefficients of Lag cut
        Rhs_model_vec = [Rhs_model(s) for s = 1:S]; # ✅ bring the RHS of Lag's cut
        JuMP.set_silent.(Sur_model_vec)
        JuMP.set_silent.(Rhs_model_vec)
        for s = 1:S # Add an initial round of cut to ensure ⚫ BOUNDEDNESS
            Μ = falses(J); Μ[1] = true; Π = falses(J) # 💡 some valid trial multipliers
            local b, m, B, M, Rhs
            Rhs = Rhs_model_vec[s]
            b, m = Rhs[:b], Rhs[:m]
            JuMP.set_objective_coefficient.(Rhs, b, Μ); JuMP.set_objective_coefficient.(Rhs, m, Π)
            JuMP.optimize!(Rhs); JuMP.assert_is_solved_and_feasible(Rhs; allow_local = false)
            B, M = JuMP.value.(b), JuMP.value.(m)
            Sur = Sur_model_vec[s]
            # because this is the very 1st cut, we obviate the need for COT-test
            JuMP.@constraint(Sur, Sur[:θ] ≤ B ⋅ Sur[:μ] + M ⋅ Sur[:π]) # 🟣 θ-Cuts
            JuMP.optimize!(Sur); JuMP.assert_is_solved_and_feasible(Sur; allow_local = false) # ⚫ BOUNDEDNESS
        end
    end)
end

@ready_Rhs_and_Sur_code()
prim_model_vec = [Min_s_prim_model(s) for s = 1:S]; # ✅ evaluate the 2nd stage ObjVals
JuMP.set_silent.(prim_model_vec);

Mst = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); # ✅ used to generate trial 1st-stage solutions (only Int solutions admit valid cut-offs)
JuMP.@variable(Mst, 0 ≤ b[1:J] ≤ 1, Int);
JuMP.@variable(Mst, 0 ≤ m[1:J], Int); JuMP.@constraint(Mst, [j = 1:J], L[j]b[j] ≥ m[j]);
JuMP.@objective(Mst, Min, o ⋅ b + u ⋅ m);
JuMP.set_silent(Mst);
JuMP.optimize!(Mst); JuMP.assert_is_solved_and_feasible(Mst; allow_local = false)
B, M = ı.(b), ı.(m); # to be globally ✂️ off
SmallHardModel = nothing
while true # iteratively find a violating cut for the Master problem
    infeasible_block = 0
    for s = 1:S # each block
        Min_s = prim_model_vec[s]; JuMP.fix.(Min_s[:m2], M)
        JuMP.optimize!(Min_s); termination = JuMP.termination_status(Min_s)
        if termination == JuMP.INFEASIBLE
            infeasible_block = s
            break # [early break]
        elseif termination != JuMP.OPTIMAL
            error("primal 2nd stage: $termination")
        end
    end
    s = infeasible_block
    if s == 0
        @info "😃 An OPTIMAL solution is derived!" ObjVal=(o ⋅ B + u ⋅ M) B M
        break
    end
    s == 0 && error("We have now derived ")
    Sur = Sur_model_vec[s]; θ, μ, π = Sur[:θ], Sur[:μ], Sur[:π];
    JuMP.set_objective_coefficient.(Sur, μ, -B)
    JuMP.set_objective_coefficient.(Sur, π, -M)
    JuMP.optimize!(Sur); JuMP.assert_is_solved_and_feasible(Sur; allow_local = false); ub = JuMP.objective_bound(Sur)
    Μ, Π = ı.(μ), ı.(π); # to be locally ✂️ off
    Θ = ı(θ); # [🪜Upper] to be locally ✂️ off
    rhs = -Inf
    while true # try generate the coefficient of Lag cut until success
        local b, m # This is NOT the (b, m) of the outermost Master
        Rhs = Rhs_model_vec[s]; b, m = Rhs[:b], Rhs[:m]
        JuMP.set_objective_coefficient.(Rhs, b, Μ); JuMP.set_objective_coefficient.(Rhs, m, Π)
        optimal = false
        for ø = 1:50
            JuMP.optimize!(Rhs)
            termination = JuMP.termination_status(Rhs)
            if termination == JuMP.TIME_LIMIT
                @info "Fail solving Rhs: $ø/100, re-pick Seed..."
                JuMP.set_attribute(Rhs, "Seed", rand(0:typemax(Int32)))
                err_code = Gurobi.GRBreset(JuMP.unsafe_backend(Rhs), 0)
                err_code == 0 || error("Gurobi reset ERROR: $err_code")
            elseif termination == JuMP.OPTIMAL
                optimal = true
                break # [early break]
            else
                error("terminate with $termination")
            end
        end
        if optimal == false
            @error "The Rhs as SmallHardModel is too hard to be solved"
            SmallHardModel = Rhs
            return SmallHardModel
        end
        rhs = JuMP.objective_value(Rhs)
        lb = rhs - B ⋅ Μ - M ⋅ Π # here (B, M) are from the outermost Master
        if lb > 4e-3 # 🟣 global COT
            @info "Find a cut for master with vio = $lb"
            break # go back to add cut for the outer Master
        end
        local_B, local_M = ı.(b), ı.(m)
        vertical_distance = Θ - (local_B ⋅ Μ + local_M ⋅ Π) # 🪜 having this since you approximate `Rhs_model_s`
        if vertical_distance > 8e-4 # 🟣 local COT
            JuMP.@constraint(Sur, θ ≤ local_B ⋅ μ + local_M ⋅ π)
        else
            error("weird 568435686586442")
        end
        JuMP.optimize!(Sur); JuMP.assert_is_solved_and_feasible(Sur; allow_local = false); ub = JuMP.objective_bound(Sur)
        Μ, Π = ı.(μ), ı.(π); # to be locally ✂️ off
        Θ = ı(θ); # [🪜Upper] to be locally ✂️ off
    end
    JuMP.@constraint(Mst, Μ ⋅ b + Π ⋅ m ≥ rhs) # 🟣 global COT
    JuMP.optimize!(Mst); JuMP.assert_is_solved_and_feasible(Mst; allow_local = false)
    B, M = ı.(b), ı.(m); # to be globally ✂️ off
    gub = o ⋅ B + u ⋅ M
    @info "Master ub = $gub"
end

I think you have a fundamental misconception about solving MIPs.

They are NP hard, but we have algorithms that are surprisingly effective in practice.

Nonetheless, there are no performance guarantees. A model may solve in 1 second, but, with a small modification, never solve in 1000 years. Solving with various seeds is one trick to exploit this performance variability, but it does not mean that there exists a seed which will solve fast.

I do not recommend your time-limit loop approach with the seeds. David’s solution was a way to demonstrate the performance variability of MIPs, it was not an algorithm. You might equally be better off using the same number of seconds with a single solve.

Re the MIP gap: this is problem and application dependent. It is up to you to decide whether it matter for your problem. We can’t provide general advice.

I think the majority is cutting planes, secondarily it’s a set of primal heuristics.
Branching is the last fallback—they finally become hopeless.

I was just wondering “Ah, you Gurobi cannot solve a model with 20 rows, 99 columns, 190 nonzeros”??? It was unimaginable for me.

I didn’t get you here. What is “using the same number of seconds”? I’m already doing this.

I find it works somehow. I update my successful code in my #7 post. You can check it out. On my computer it runs about 5 minutes and the global optimal solution is found. (This is a Benders decomposition for 2 stage stochastic programming having general integer variables on both stages).

Yes. This should be expected. There are NO performance guarantees, even for tiny MIPs.

For example, many open instances in the MIPLIB have a modest number of variables/constraints: The Open Tag. Similar for the hard instances.

It sometimes helps to try to reformulate the original problem with an alternative formulation in the hopes that it is easier to solve