MIPSOL Callback used to add constraints and repaired solution
OngoingHi all,
I’m working on an MILP model where I track flow variables representing how much resource moves from one task to another. In my formulation, I partially relax the model by allowing flows between tasks as long as their ordering (represented by other variable) is respected. However, I do not initially enforce the detailed time feasibility of those flows (processing + transfer times) which noticeably reduces complexity.
To enforce those missing time/flow constraints only when needed, I use a callback with where == gp.GRB.Callback.MIPSOL.
Inside the callback:
- If the current solution respects all time/flow constraints, I simply
return. - If the solution violates them:
- I add lazy constraints via
cbLazyto rule out the invalid flow/time assignments. - I repair the violating solution using a custom heuristic.
- I then try to feed this repaired solution back into Gurobi using:
model.cbSetSolution(vars, vals) # called for all used tupledicts of “vars”
model.cbUseSolution()
Problem:
The repaired solution never gets used.
- The value returned by calling
cbUseSolution()is1e101(infinity) after setting all variables. - There’s no error, warning, or log output.
- If I warm-start the solver outside of the callback using the exact same repaired solution, it works perfectly and is accepted.
My suspicion:
Because the repaired solution always has an equal or worse objective than the original incumbent, Gurobi skips evaluating it and just keeps the current incumbent, even though that incumbent has now become infeasible due to the newly added lazy constraints.
Questions:
- How can I force Gurobi to drop the invalid incumbent after adding
cbLazyconstraints? - Is there a way to replace the incumbent with my repaired solution, even when it has a worse objective?
- If that’s impossible, is there a way to mark all previous incumbents as invalid once I add the new lazy constraints, so that Gurobi reconsiders feasible solutions like my repaired one?
Environment:
- Gurobi version: 12.0.3
- Language: Python
- Model type: MILP
- Using callbacks:
MIPSOL+cbLazy+cbSetSolution+cbUseSolution - Any guidance on how to properly handle this would be greatly appreciated.
Also, if devs read this, the logging and overall documentation of this is not very good. It would be very helpful if at least some output messages would confirm that the solution was loaded, solved, rejected and so on.
-
Hi Vilém,
If you use and add lazy constraints through a callback, you must set the parameter LazyConstraints=1. Then, no solution that violates any of these lazy constraints should be accepted. So, you cannot see any solutions in the log output that “need to be repaired”.
You could compare with the tsp example of how lazy constraints are added in a callback.
In the MIPSol callback, a potential solution is checked (you could get the objective with the What value MIPSOL_OBJ). If you add lazy constraints because they are violated by this solution, the solution is rejected (and does not appear in the log file).
You could create your own solution with cbUseSolution() and cbSetSolution() as you mentioned. Note that cbUseSolution() will return GRB.INFINITY when called in the MIPSOL callback because Gurobi cannot process it immediately (as commented in the documentation).
If you still have issues with this. Please share your callback code and the log output.
You could add your own logging in your callback. It is on purpose that there are no default log lines for added solutions. This could flood the log and could hurt performance.
Do you have more concrete ideas on how we could improve the documentation?Best regards,
Marika0 -
Hi Marika,
sorry I did not provide code, it is part of ongoing research so I am not in a liberty to share it. I am using LazyConstraints=1.
I did some larger scale testing and found out that it probably works by comparing logs with and without setSolution() use; objectives do update differently (but sometimes it takes some time as you said).
Could you provide more information when exactly the solution gets assigned by useSolution() when using MIPSOL trigger?My main issue with this functionality is little feedback given to the programmer, so I propose these changes to improve its use and clarity.
- setSolution() can return boolean or possibly even string message (can be triggered by a parameter of the setSolution() function) containing if assignment was successful and possibly which variable got which value (provides clear feedback that something is happening).
- Or possibly there can be multiple levels of output flag, and things that could “flood” the output can be on some finee level.
- useSolution() could take parameter “forceEvaluation” that if set True, it would evaluate solution right away given the current model's constraints. So for debugging one could use this but in production it would not make the model slower. In the documentation, there is: “You can also optionally call
cbUseSolutionwithin your callback function to try to immediately compute a feasible solution from the specified values.” I think this is little bit misleading at the moment. - Generally, parameters of functions could have supported types described in the documentation. I was not sure if I can for example pass values as tupledicts. ChatGPT was forcing me to use list, but I found out that tupledicts seem to be fine.
Kind regards,
Vilém
0
Please sign in to leave a comment.
Comments
2 comments