Callback Setsolution from a MIP solution node
AnsweredI am solving a two stage stochastic program (in the extensive or the "deterministic" form) with integer variables in both the stages. I observe that Gurobi's heuristics find good firststage solutions fast, but the second stage solutions are suboptimal.
But given the firststage solution, it is easy to solve a secondstage problem. So, I would like to do the following:
Whenever gurobi finds a feasible solution, (GRB.Callback.MIPSOL), I would like to retrieve the firststage solutions (by using Model.cbGetSolution), and solve the 100 much smaller secondstage MIPs, and feed the solution of the second stage to gurobi (using Model.cbSetSolution and cbUseSolution). I believe this could considerably improve the objective value of the current solution.
My problem is that Model.cbGetSolution is allowed only if where==GRB.Callback.MIPSOL while Model.cbSetSolution is allowed only if where == GRB.Callback.MIPNODE.
Is there a workaround for this problem? I am using the Python API.

I am facing the same issue.
Is there any solution for that?
1 
Hello, I am facing the same question, the only thing I could find cb.GetNodeRel() can be used to retrieve the variables of the relaxation but I am unsure if it actually works for you, would be helpful if someone could share how can this be solved
0 
In the next major or minor Gurobi release, you will be able to set solutions from the \( \texttt{MIPSOL} \) callback. In the meantime, a workaround is to store the solution, then pass it to Gurobi in the next \( \texttt{MIPNODE} \) callback.
1 
How can you have two callbacks in one function? to my understanding once you set the first \( \texttt{if} \) block, that's the only one it gets to run:
def mycallback(model, where):
if where == GRB.Callback.MIPSOL:
v = model.cbGetSolution(model._vars)
vs = model._vars
vi = v/(v+3)
elif where == GRB.Callback.MIPNODE:
model.cbLazy(lhs,sense,rhs)
model.cbSetSolution(vs,vi)
model.optimize(mycallback)0 
Your callback can definitely handle multiple `where` values with if statements.
0 
You can generate and store the solution when \( \texttt{mycallback} \) is called with \( \texttt{where=MIPSOL} \), then set the solution the next time \( \texttt{mycallback} \) is called with \( \texttt{where=MIPNODE} \). So the solution is generated and set in two different calls to \( \texttt{mycallback} \). E.g.:
def mycallback(model, where):
if where == GRB.Callback.MIPSOL:
vals = model.cbGetSolution(model._vars)
model._vals = [v / (v + 3) for v in vals]
elif where == GRB.Callback.MIPNODE and model._vals is not None:
model.cbSetSolution(model._vars, model._vals)
model._vals = None
model._vars = model.getVars()
model._vals = None
model.optimize(mycallback)1 
Thanks a lot! I am trying to implement this in the model I am working on, when I tried to use \( \texttt{where=MIPNODE} \) nothing seemed to happen, then I checked for the condition to see if it's being read, if the statement is true then the block inside that \(\texttt{elif}\) will be executed
print(str(where==GRB.Callback.MIPNODE))
However, the result I get back is
False
So,
model.cbSetSolution(vars, solution)
won't get executed, my question is, what could make the \(\texttt{GRB.Callback.MIPNODE}\) false?
0 
Note that \( \texttt{where} \) is equal to \( \texttt{GRB.Callback.MIPSOL} \) when the solution is set, then \( \texttt{mycallback} \) will be called later (separately) with a \( \texttt{where} \) value of \( \texttt{GRB.Callback.MIPNODE} \). \( \texttt{where==GRB.Callback.MIPNODE} \) is \( \texttt{false} \) whenever \( \texttt{mycallback} \) is called with a non\( \texttt{MIPNODE} \) value of \( \texttt{where} \) (see the Callback Codes section of the documentation).
Is \( \texttt{mycallback} \) never called with \( \texttt{where==GRB.Callback.MIPNODE} \)? It would be helpful to see the log output from Gurobi and your code for a minimal working callback function.
0 
Hello,
Thanks, I could see what I was doing wrong. Now my code enters \( \texttt{where=MIPNODE} \) and I can make use of \(\texttt{cb.SetSolution}\). However, I also want to use \(\texttt{Model.UseSolution()}\), and when printing to check what value I am about to pass I get 1e+100. What does this value mean and what kind of heuristic does gurobi implement at this point?
0 
The return value of Model.cbUseSolution() is \( \texttt{GRB.INFINITY} \) (1e+100) if Gurobi isn't able to compute an improved solution using the variable values specified in Model.cbSetSolution(). Are you sure your solution is feasible and better than the current incumbent objective value?
Gurobi solves an optimization problem to try to "fill in" any missing variable values. The solution you give to Gurobi generally comes from your own heuristic algorithm.
0 
Hi Everyone,
Gurobi 9.5 was recently released. Included in this release is the ability to set a new solution from the MIP and the MIPSOL callbacks. Therefore, there is no need to store a solution internally and wait for the next MIPNODE callback anymore.
Unfortunately, this new feature is missing in the Gurobi 9.5 documentation. It will be added in the next technical release.
We hope this new feature works well for you. Please let us know if you find any issues using this.
Best regards,
Maliheh
0 
Dear all,
when useSolution() tries to produce an heuristic solution, does it check if it is feasible also w.r.t branching constraints added during the branchandcut algorithm?
I am asking because I am sure to have a feasible solution which improves over the incumbent, which gets surely accepted by useSolution (i.e., that does not return INFINITY) only as long as I am in the root node, while during the branching it may or may not be accepted.
If this is the case, how can I know which are the branching constraints added in the MIPNODE in which I'm feeding the solution? With this information I can modify accordingly my heuristic solution and use it.
Thanks!0 
Hi,
Same issue here.
To solve a MINLP, I run Gurobi's B&C on a MILP relaxation and, at each MIPSOL, I either cutoff the integer node with a combinatorial cut or provides the actual MINLP solution if exists. With the oldest releases, I used the trick to store the solution and wait for the next MIPNODE event to post it. Now, I updated my code as solutions can now be set at MIPSOL. However, cbSetSolution/cbUseSolution keep rejecting the solutions I provide, and which are feasible (I checked them by cloning the model including all the lazy constraints and fixing the variables).
Systematically, the callback is called twice at these MIPSOL nodes but with a different solution (the fractional part now satisfies the newly added lazy cut, but it is still not feasible for my problem).
I also tried to enforce just partial solutions (only the integer part, or the fractional, or just one variable)... but cbUseSolution keeps returning 1e+101 anyway. I really do not see what I am doing wrong. As a feature request: could it be possible to get more information from cbUseSolution() when it rejects a solution ?
1 
Hi, here is an update:
I confirm that, in my code, setSolution/useSolution never works at where=MIPSOL (i.e. useSolution returns 1e+101) but it always does (i.e. useSolution returns the actual cost) when I store the solution and set/use it at the next where=MIPNODE.
Now I have another problem in 1 of my test (among a dozen): useSolution "accepts" a solution at where=MIPNODE (i.e. it returns its actual cost) but the incumbent is not updated accordingly. Do you know what could happen and how to know in the callback when a solution is actually "used" ? Another question relative to this one: why MIPSOL_OBJBST returns the value of the current MIP solution and not the value of the incumbent at this point (given that the MIP solution can be cutoff in the callback) ? And could we have access to this (previous) incumbent value at where=MIPSOL ?
Thank you.
2 
when useSolution() tries to produce an heuristic solution, does it check if it is feasible also w.r.t branching constraints added during the branchandcut algorithm?
I am asking because I am sure to have a feasible solution which improves over the incumbent, which gets surely accepted by useSolution (i.e., that does not return INFINITY) only as long as I am in the root node, while during the branching it may or may not be accepted.
If this is the case, how can I know which are the branching constraints added in the MIPNODE in which I'm feeding the solution? With this information I can modify accordingly my heuristic solution and use it.If the solution is feasible to the constraints of the original problem, it should be accepted. Are you using Gurobi 10.0.1? If so, are you able to construct a minimal working example that reproduces this behavior?
I also tried to enforce just partial solutions (only the integer part, or the fractional, or just one variable)... but cbUseSolution keeps returning 1e+101 anyway. I really do not see what I am doing wrong. As a feature request: could it be possible to get more information from cbUseSolution() when it rejects a solution ?
Do you mean that Model.cbUseSolution() always returns a value of 1e+101 when called in the \(\texttt{GRB.Callback.MIP}\) or \(\texttt{GRB.Callback.MIPSOL}\) callbacks? If so, this is expected. From the Model.cbUseSolution() documentation:
Return value:
The objective value for the solution obtained from your solution values. It equals GRB.INFINITY if no improved solution is found or the method has been called from a callback other than GRB.Callback.MIPNODE.
When Model.cbUseSolution() is called from the \(\texttt{GRB.Callback.MIP}\) or \(\texttt{GRB.Callback.MIPSOL}\) callbacks, Gurobi stores the solution until it can be processed. Does the log indicate that the solution is later accepted?
Another question relative to this one: why MIPSOL_OBJBST returns the value of the current MIP solution and not the value of the incumbent at this point (given that the MIP solution can be cutoff in the callback) ?
This is a bug. A fix will be available in Gurobi 10.0.2. Thanks for letting us know!
1 
If the solution is feasible to the constraints of the original problem, it should be accepted. Are you using Gurobi 10.0.1? If so, are you able to construct a minimal working example that reproduces this behavior?
Sure, in the following you can find the log of my working example. As can be seen, I provide gurobi with an initial solution of value 18084.6. "Staggering vehicle" is the move of my local search, that tries to reduce the total value of the solution by rescheduling the departure time of one vehicle of my fleet at the time. Current total delay is the value of the heuristic solution. As long as I am in the root node the heuristic solutions that I find are accepted (unless gurobi finds a better heuristic solution in a shorter amount of time). The solution that I provide with value 5327.73 is not accepted by useSolution(), and this happens after gurobi started branching. What I do is then calling model.terminate() and restart the search using the solution with value 5327.73 as warm start, which is in this case accepted. Maybe it is worth to mention that I always provide values for integer and fractional variables:
Academic license  for noncommercial use only  expires 20231105
Creating conflict variables
Creating continuous variables
Adding conflict constraints
Writing the continuity constraints
Writing the objective function
saved unaccepted heuristic solution
Set parameter TimeLimit to value 1.7832646110057831e+03
Set parameter MIPGap to value 0
Set parameter NodefileStart to value 0.5
Set parameter MIPFocus to value 2
Set parameter NumericFocus to value 2
Set parameter FeasibilityTol to value 1e08
Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)
CPU model: 11th Gen Intel(R) Core(TM) i51135G7 @ 2.40GHz, instruction set [SSE2AVXAVX2AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
Optimize a model with 82376 rows, 106249 columns and 279747 nonzeros
Model fingerprint: 0x47941e89
Model has 113776 general constraints
Variable types: 24637 continuous, 81612 integer (81612 binary)
Coefficient statistics:
Matrix range [5e01, 1e+00]
Objective range [1e+00, 1e+00]
Bounds range [1e+00, 7e+03]
RHS range [1e+00, 7e+03]
PWLCon x range [1e+00, 7e+00]
PWLCon y range [0e+00, 4e+01]
GenCon rhs range [1e03, 1e03]
GenCon coe range [1e+00, 1e+00]
User MIP start produced solution with objective 18084.6 (0.50s)
Loaded user MIP start with objective 18084.6
Presolve removed 181263 rows and 229143 columns (presolve time = 6s) ...
Presolve removed 262189 rows and 263764 columns (presolve time = 34s) ...
Presolve removed 45152 rows and 79076 columns
Presolve time: 33.65s
Presolved: 37224 rows, 27173 columns, 144564 nonzeros
Presolved model has 2319 SOS constraint(s)
Found heuristic solution: objective 13947.533433
Variable types: 12904 continuous, 14269 integer (14268 binary)
Root relaxation presolve removed 207 rows and 238 columns
Root relaxation presolved: 37017 rows, 26935 columns, 144045 nonzeros
Deterministic concurrent LP optimizer: primal and dual simplex
Showing first log only...
Root simplex log...
Iteration Objective Primal Inf. Dual Inf. Time
0 3.1365904e+03 8.387912e+03 4.341447e+09 35s
14061 4.0869493e+03 1.333196e+01 5.275585e+07 35s
16826 1.3307845e+03 0.000000e+00 1.927024e+03 35s
18385 7.5599080e+02 0.000000e+00 0.000000e+00 36s
Concurrent spin time: 1.47s
Solved with primal simplex
18385 7.5599080e+02 0.000000e+00 0.000000e+00 37s
Root relaxation: objective 7.559908e+02, 18385 iterations, 2.75 seconds (0.95 work units)
Nodes  Current Node  Objective Bounds  Work
Expl Unexpl  Obj Depth IntInf  Incumbent BestBd Gap  It/Node Time
0 0 755.99080 0 705 13947.5334 755.99080 94.6%  39s
0 0 755.99080 0 708 13947.5334 755.99080 94.6%  39s
0 0 755.99080 0 708 13947.5334 755.99080 94.6%  39s
H 0 0 12827.749905 755.99080 94.1%  40s
 staggering vehicle 178 by 5.32336  current total delay: 12800.5 > OldDelay  NewDelay = 27.259
 staggering vehicle 193 by 26.8083  current total delay: 12787.7 > OldDelay  NewDelay = 12.7964
 staggering vehicle 1257 by 9.1239  current total delay: 12775.9 > OldDelay  NewDelay = 11.7862
setting heuristic solution in callback
H 0 0 9992.8697310 785.61880 92.1%  42s
0 0 793.51342 0 1051 9992.86973 793.51342 92.1%  43s
0 0 793.51342 0 1051 9992.86973 793.51342 92.1%  43s
 staggering vehicle 38 by 17.3183  current total delay: 9961 > OldDelay  NewDelay = 31.8746
 staggering vehicle 211 by 19.374  current total delay: 9944.04 > OldDelay  NewDelay = 16.9569
 staggering vehicle 95 by 4.96945  current total delay: 9931.15 > OldDelay  NewDelay = 12.8843
 staggering vehicle 46 by 19.1771  current total delay: 9911.21 > OldDelay  NewDelay = 19.9416
 staggering vehicle 212 by 38.2075  current total delay: 9875.98 > OldDelay  NewDelay = 35.2322
 staggering vehicle 724 by 12.7865  current total delay: 9857.36 > OldDelay  NewDelay = 18.6155
setting heuristic solution in callback
0 0 810.03454 0 1028 9992.86973 810.03454 91.9%  44s
H 0 0 9857.3647500 811.12093 91.8%  45s
0 0 811.12093 0 1026 9857.36475 811.12093 91.8%  45s
0 0 854.25190 0 1020 9857.36475 854.25190 91.3%  45s
0 0 869.63497 0 1133 9857.36475 869.63497 91.2%  49s
0 0 870.64213 0 1122 9857.36475 870.64213 91.2%  49s
0 0 870.64213 0 1122 9857.36475 870.64213 91.2%  49s
H 0 0 8540.3112840 872.89051 89.8%  50s
0 0 872.89051 0 1144 8540.31128 872.89051 89.8%  50s
0 0 872.89051 0 1145 8540.31128 872.89051 89.8%  50s
 staggering vehicle 781 by 68.6936  destaggering vehicle 722 by 22.3973  current total delay: 8485.77 > OldDelay  NewDelay = 54.5431
 staggering vehicle 1235 by 34.4721  current total delay: 8473.08 > OldDelay  NewDelay = 12.6875
 staggering vehicle 38 by 17.3183  current total delay: 8441.21 > OldDelay  NewDelay = 31.8746
 staggering vehicle 95 by 4.96945  current total delay: 8428.32 > OldDelay  NewDelay = 12.8843
 staggering vehicle 1304 by 13.6961  current total delay: 8418.14 > OldDelay  NewDelay = 10.1808
 staggering vehicle 1258 by 1.56485  current total delay: 8415.8 > OldDelay  NewDelay = 2.33793
 staggering vehicle 931 by 2.68681  current total delay: 8409.71 > OldDelay  NewDelay = 6.09559
 staggering vehicle 1069 by 4.76216  current total delay: 8313.88 > OldDelay  NewDelay = 95.8227
 staggering vehicle 498 by 14.6169  current total delay: 8310.3 > OldDelay  NewDelay = 3.58557
setting heuristic solution in callback
0 0 872.92139 0 1146 8540.31128 872.92139 89.8%  52s
H 0 0 8310.2992920 872.92139 89.5%  52s
0 0 872.92139 0 1144 8310.29929 872.92139 89.5%  52s
0 0 907.59290 0 652 8310.29929 907.59290 89.1%  60s
H 0 0 6624.8816040 907.59290 86.3%  60s
H 0 0 6468.2387820 907.59290 86.0%  61s
H 0 0 5498.4590460 907.59290 83.5%  68s
 staggering vehicle 904 by 17.6209  current total delay: 5481.47 > OldDelay  NewDelay = 16.9901
 staggering vehicle 814 by 29.8285  current total delay: 5459.35 > OldDelay  NewDelay = 22.1203
setting heuristic solution in callback
3 4 953.57219 2 120 5498.45905 953.57219 82.7% 14708 75s
H 514 154 5366.8360710 974.57161 81.8% 97.7 80s
 staggering vehicle 904 by 17.6209  current total delay: 5349.85 > OldDelay  NewDelay = 16.9901
 staggering vehicle 814 by 29.8285  current total delay: 5327.73 > OldDelay  NewDelay = 22.1203
setting heuristic solution in callback
Heuristic solution has not been accepted  terminating MIP model
saved unaccepted heuristic solution
Cutting planes:
Learned: 13
Liftandproject: 68
Cover: 581
Implied bound: 201
Projected implied bound: 1
Clique: 545
MIR: 286
StrongCG: 8
Flow cover: 1491
Zero half: 13
Network: 1
RLT: 172
Relaxandlift: 126
Explored 515 nodes (119243 simplex iterations) in 86.43 seconds (42.35 work units)
Thread count was 8 (of 8 available processors)
Solution count 10: 5366.84 5498.46 6468.24 ... 13947.5
Solve interrupted
Best objective 5.366836070999e+03, best bound 1.003840565559e+03, gap 81.2955%
Usercallback calls 6672, time in usercallback 8.19 sec
Set parameter TimeLimit to value 1.6964470658302307e+03
Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)
CPU model: 11th Gen Intel(R) Core(TM) i51135G7 @ 2.40GHz, instruction set [SSE2AVXAVX2AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
Optimize a model with 82376 rows, 106249 columns and 279747 nonzeros
Model fingerprint: 0xe041006f
Model has 113776 general constraints
Variable types: 24637 continuous, 81612 integer (81612 binary)
Coefficient statistics:
Matrix range [5e01, 1e+00]
Objective range [1e+00, 1e+00]
Bounds range [1e+00, 7e+03]
RHS range [1e+00, 7e+03]
PWLCon x range [1e+00, 7e+00]
PWLCon y range [0e+00, 4e+01]
GenCon rhs range [1e03, 1e03]
GenCon coe range [1e+00, 1e+00]
User MIP start produced solution with objective 5327.73 (0.32s)
Loaded user MIP start with objective 5327.73
MIP start from previous solve did not produce a new incumbent solution
[...]Thank you for your help!
1 
That looks pretty suspicious. I am assuming you are calling Model.cbUseSolution() in the \(\texttt{MIPNODE}\) callback and declaring the heuristic solution to be rejected if Model.cbUseSolution() returns \(\texttt{GRB.INFINITY}\). Are you able to share a minimal working code example?
0 
I am assuming you are calling Model.cbUseSolution() in the \(\texttt{MIPNODE}\) callback and declaring the heuristic solution to be rejected if Model.cbUseSolution() returns \(\texttt{MIPNODE}\).
This is correct.
Are you able to share a minimal working code example?
This behaviour arises only when the size of the instances I am trying to solve is large, so it is not really possible to provide a minimal working example. I can however share my callback function:
 I retrieve \(\texttt{model._cbReleaseTimes}\) in \(\texttt{MIPSOL}\) using \(\texttt{model.cbGetSolution()}\);
 I use these values to produce the heuristic solution in the subsequent \(\texttt{MIPNODE}\) with \(\texttt{getHeuristicSolution()}\);
 In \(\texttt{setHeuristicSolution()}\), I set the values for my binary and continuous variables using \(\texttt{model.cbSetSolution()}\) (in the following the respective functions). Thereafter, I call \(\texttt{model.cbUseSolution()}\). If the return value of the latter is \(\texttt{1e+100}\), then I save the heuristic solution in an external file, terminate gurobi, and restart the model using the saved solution as warm start.
def callback(instance: Instance, statusQuo: CompleteSolution) > Callable:
def callLocalSearch(model, where) > None:
if where == grb.GRB.Callback.MIPSOL:
model._cbReleaseTimes = [model.cbGetSolution(model._departure[vehicle][path[0]])
for vehicle, path in enumerate(instance.arcBasedShortestPaths)]
if where == grb.GRB.Callback.MIPNODE:
heuristicSolution = getHeuristicSolution(model, instance)
setHeuristicSolution(model, heuristicSolution, instance)
return callLocalSearchdef setHeuristicSolution(model: Model, heuristicSolution: HeuristicSolution, instance: Instance) > None:
print("setting heuristic solution in callback")
setHeuristicBinaryVariables(model, heuristicSolution)
setHeuristicContinuousVariables(model, heuristicSolution)
returnValUseSolution = model.cbUseSolution()
if returnValUseSolution == 1e+100:
print("Heuristic solution has not been accepted  terminating MIP model")
suspendProcedure(heuristicSolution, model, instance)
returndef setHeuristicBinaryVariables(model, heuristicSolution):
for arc in model._gamma:
for firstVehicle, secondVehicle in itertools.combinations(model._gamma[arc], 2):
model.cbSetSolution(model._alpha[arc][firstVehicle][secondVehicle],
heuristicSolution.binaries.alpha[arc][firstVehicle][secondVehicle])
model.cbSetSolution(model._beta[arc][firstVehicle][secondVehicle],
heuristicSolution.binaries.beta[arc][firstVehicle][secondVehicle])
model.cbSetSolution(model._gamma[arc][firstVehicle][secondVehicle],
heuristicSolution.binaries.gamma[arc][firstVehicle][secondVehicle])
model.cbSetSolution(model._alpha[arc][secondVehicle][firstVehicle],
heuristicSolution.binaries.alpha[arc][secondVehicle][firstVehicle])
model.cbSetSolution(model._beta[arc][secondVehicle][firstVehicle],
heuristicSolution.binaries.beta[arc][secondVehicle][firstVehicle])
model.cbSetSolution(model._gamma[arc][secondVehicle][firstVehicle],
heuristicSolution.binaries.gamma[arc][secondVehicle][firstVehicle])def setHeuristicContinuousVariables(model, heuristicSolution):
for vehicle in model._departure:
for position, arc in enumerate(model._departure[vehicle]):
model.cbSetSolution(model._departure[vehicle][arc],
heuristicSolution.congestedSchedule[vehicle][position])
model.cbSetSolution(model._delay[vehicle][arc], heuristicSolution.delaysOnArcs[vehicle][position])Thank you for your help.
0 
We are very interested in investigating this issue further, though this requires being able to reproduce the issue. I will open a support request for you to discuss how we can replicate the error.
1 
Is there any update regarding the issue?
I experience the same problem. I find an improved solution in the callback, but Gurobi rejects it. However, if I use the same solution to warm start, the MIP it works fine.
0 
Our development team is aware of the issue described by Antonio. I unfortunately can't make any guarantees at this time about if/when a fix will become available.
For that particular issue, a workaround is to set the Disconnected parameter to 0. Could you check if setting the Disconnected parameter to 0 resolves the issue? If not, please post the Gurobi log and a minimal version of your callback code.
0
Please sign in to leave a comment.
Comments
21 comments