How do I add cuts during the solve process when using BilevelJuMP.jl
. My upper-level objective is a sum of binary variables and thus I know that my solution (optimal objective value) is eventually going to be an integer value. However, when I look at the log of Gurobi, I see that it often struggles to improve the objective bound, and I want to add a cut based on the current objective bound. To explain further, consider the following Gurobi log:
Root relaxation: objective 8.998468e+01, 518 iterations, 0.01 seconds
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
0 0 89.98468 0 10 - 89.98468 - - 0s
0 0 89.98234 0 14 - 89.98234 - - 0s
0 0 89.97892 0 13 - 89.97892 - - 0s
0 0 89.96860 0 16 - 89.96860 - - 0s
0 0 89.94980 0 19 - 89.94980 - - 0s
0 0 89.94980 0 19 - 89.94980 - - 0s
0 0 89.94980 0 22 - 89.94980 - - 0s
0 0 89.94980 0 22 - 89.94980 - - 0s
0 0 89.94980 0 23 - 89.94980 - - 0s
H 0 0 78.0000000 89.94980 15.3% - 0s
0 2 89.94980 0 23 78.00000 89.94980 15.3% - 0s
H 31 40 79.0000000 89.94980 13.9% 66.9 0s
H 79 88 82.0000000 89.94980 9.69% 48.9 0s
H 230 238 83.0000000 89.91294 8.33% 34.7 1s
4487 2368 87.55668 29 20 83.00000 88.54009 6.67% 67.9 5s
10587 6671 85.95524 32 21 83.00000 87.92902 5.94% 66.2 10s
17385 11235 84.89499 41 65 83.00000 87.87144 5.87% 73.7 15s
23324 14212 84.29968 43 59 83.00000 87.78450 5.76% 74.9 23s
24901 15632 85.96120 35 13 83.00000 87.74486 5.72% 74.4 25s
32564 20416 84.67600 54 72 83.00000 87.56339 5.50% 74.8 30s
38959 24058 84.95694 35 77 83.00000 87.33329 5.22% 74.7 35s
47877 28915 86.30447 31 101 83.00000 87.07891 4.91% 73.5 40s
56460 34265 cutoff 51 83.00000 86.97277 4.79% 72.2 45s
62710 38323 85.75822 32 19 83.00000 86.96494 4.78% 71.4 50s
69402 42717 85.67587 32 25 83.00000 86.95674 4.77% 71.8 55s
77405 47259 85.84095 43 22 83.00000 86.94781 4.76% 70.7 60s
84003 51593 85.85930 40 18 83.00000 86.94201 4.75% 70.8 65s
91384 56395 84.98545 33 10 83.00000 86.93370 4.74% 70.3 70s
100029 60781 85.88272 42 67 83.00000 86.92359 4.73% 70.4 76s
107143 64839 84.65086 28 74 83.00000 86.91290 4.71% 70.1 80s
113864 68059 84.43224 59 48 83.00000 86.89911 4.70% 70.2 85s
120449 71624 cutoff 53 83.00000 86.88479 4.68% 70.5 90s
127217 75347 85.82191 38 23 83.00000 86.86795 4.66% 70.4 95s
133858 78801 84.02847 44 51 83.00000 86.84816 4.64% 70.5 100s
138806 81250 85.95811 39 15 83.00000 86.83431 4.62% 70.5 105s
145960 85375 85.87520 42 18 83.00000 86.81807 4.60% 70.7 110s
152275 89068 85.55562 29 28 83.00000 86.80187 4.58% 70.7 115s
159666 92576 85.54116 31 101 83.00000 86.78135 4.56% 70.8 120s
166000 96009 85.52644 45 62 83.00000 86.76866 4.54% 70.9 125s
172782 98758 85.28485 42 55 83.00000 86.75497 4.52% 71.1 130s
179430 102455 84.95709 33 21 83.00000 86.73603 4.50% 71.1 135s
186111 105424 84.95057 44 14 83.00000 86.71476 4.48% 71.2 141s
190993 108810 85.58797 45 12 83.00000 86.70265 4.46% 71.1 145s
198359 111618 85.59287 37 67 83.00000 86.68456 4.44% 70.9 153s
198377 112559 85.51505 38 63 83.00000 86.68395 4.44% 70.9 155s
205236 116157 85.89955 23 28 83.00000 86.66800 4.42% 70.8 160s
212192 120081 84.33530 53 46 83.00000 86.65369 4.40% 70.8 165s
218863 123904 85.97682 29 22 83.00000 86.63594 4.38% 70.8 170s
226166 127846 86.50704 38 22 83.00000 86.61843 4.36% 70.7 175s
232597 131329 85.02548 44 83 83.00000 86.60379 4.34% 70.8 180s
240631 135281 84.69241 33 77 83.00000 86.58375 4.32% 70.9 186s
245577 138076 85.28130 42 58 83.00000 86.57491 4.31% 71.0 190s
252705 141379 85.90826 37 21 83.00000 86.55902 4.29% 71.1 195s
259339 144601 84.90111 48 72 83.00000 86.54584 4.27% 71.2 200s
266309 148418 85.98033 22 20 83.00000 86.52943 4.25% 71.3 205s
272807 151235 85.91119 38 26 83.00000 86.51782 4.24% 71.3 211s
278635 154397 84.80089 44 63 83.00000 86.50499 4.22% 71.3 215s
286104 158498 cutoff 59 83.00000 86.48901 4.20% 71.2 220s
292976 162017 85.75805 32 89 83.00000 86.47756 4.19% 71.2 225s
300377 165168 cutoff 47 83.00000 86.46300 4.17% 71.1 230s
306389 168831 84.93951 47 19 83.00000 86.45334 4.16% 71.2 235s
313521 172557 cutoff 47 83.00000 86.43800 4.14% 71.2 241s
321063 175959 85.80951 40 16 83.00000 86.42237 4.12% 71.1 246s
325538 178310 84.94510 41 72 83.00000 86.41506 4.11% 71.2 250s
332609 181354 85.16023 39 67 83.00000 86.40126 4.10% 71.2 259s
332631 182064 85.13804 40 67 83.00000 86.40099 4.10% 71.2 261s
337444 184433 84.53113 47 65 83.00000 86.39368 4.09% 71.3 265s
342821 187027 84.79693 41 47 83.00000 86.38475 4.08% 71.3 270s
347751 189828 84.96194 49 63 83.00000 86.37539 4.07% 71.4 275s
353484 192295 85.87731 34 86 83.00000 86.36465 4.05% 71.4 280s
358339 195075 85.36642 38 100 83.00000 86.35668 4.04% 71.4 285s
364166 197676 85.84782 36 20 83.00000 86.34536 4.03% 71.5 290s
371185 200969 84.68117 52 56 83.00000 86.33693 4.02% 71.6 296s
376339 203420 cutoff 41 83.00000 86.32835 4.01% 71.7 300s
381610 205712 85.77387 28 28 83.00000 86.31993 4.00% 71.7 305s
389399 209002 84.53351 44 59 83.00000 86.30526 3.98% 71.7 311s
394705 211597 84.75263 38 63 83.00000 86.29478 3.97% 71.7 315s
399615 214150 84.97995 43 7 83.00000 86.28584 3.96% 71.7 320s
407286 217533 84.68814 45 68 83.00000 86.26921 3.94% 71.6 326s
In the above snippet, we can see that the objective bound is between 86 and 87 for a long time. But, an objective bound of 86.xx
implies that my objective can only be <=86
. So, when the objective bound takes these non-integer values, then I want to add a cut that can do something like floor(current_objective_bound)
. I believe I can add user cuts as is described here; however, it doesn’t work as intended because my model
is a BilevelJuMP
model and not a JuMP
model?
If an MWE is needed (y
is the integer variable here):
using JuMP, BilevelJuMP, Gurobi
model = BilevelModel(Gurobi.Optimizer, mode = BilevelJuMP.SOS1Mode())
@variable(Lower(model), x)
@variable(Upper(model), y, Int)
@objective(Upper(model), Min, y)
@constraints(Upper(model), begin
x <= 5
y <= 8
y >= 0
end)
@objective(Lower(model), Min, -x)
@constraints(Lower(model), begin
x + y <= 8
4x + y >= 8
2x + y <= 13
2x - 7y <= 0
end)
function my_callback_function(cb_data)
y_val = callback_value(cb_data, y)
con = @build_constraint(y<= floor(y_val))
println("Adding $(con)")
MOI.submit(model, MOI.UserCut(cb_data), con)
end
MOI.set(model, MOI.UserCutCallback(), my_callback_function)
The last line throws the following error as expected:
ERROR: MethodError: no method matching set(::BilevelModel, ::MathOptInterface.UserCutCallback, ::typeof(my_callback_function))
Is there a workaround to this problem?
Any help is appreciated. Thanks.