Useful Gurobi features you may not know
Optimization Support Manager
Real-world optimization problems often have multiple, competing objectives:
Maximize Profit
&
Minimize Late Orders
Minimize Shift Count
&
Maximize Worker Satisfaction
Minimize Cost
&
Maximize Product Durability
Maximize Profit
&
Minimize Risk
\[ \begin{align*} \min & ~w_1 f_1(x) + w_2 f_2(x) + w_3 f_3(x) \\ \text{s. t. } & ~x \in C \end{align*} \]
\[ \begin{align*} \min & ~f_1(x) \\ \text{s. t. } & ~x \in C \end{align*} \]
\[ \begin{align*} \min & ~f_2(x) \\ \text{s. t. } & ~x \in C \\ & ~f_1(x) \leq \epsilon_1 \end{align*} \]
\[ \begin{align*} \min & ~f_3(x) \\ \text{s. t. } & ~x \in C \\ & ~f_1(x) \leq \epsilon_1 \\ & ~f_2(x) \leq \epsilon_2 \end{align*} \]
model.setObjectiveN(expr, index, priority=0, weight=1,
abstol=1e-6, reltol=0, name="")
# expr (LinExpr): New alternative objective
# index (int): Index for new objective (to set parameters or
# query solution per objective)
# priority (int, optional): Objective's priority (ObjNPriority attribute)
# weight (float, optional): Objective's weight (ObjNWeight attribute)
# abstol (float, optional): Absolute tolerance used in calculating
# allowable degradation (ObjNAbsTol attribute)
# reltol (float, optional): Relative tolerance used in calculating
# allowable degradation (ObjNAbsTol attribute)
# name (string, optional): Objective's name
Workforce Scheduling:
# Set global sense for ALL objectives
model.ModelSense = GRB.MINIMIZE
# Set up primary objective: minimize total pay cost
model.setObjectiveN(
gp.quicksum(pay[w] * x[w, s] for w, s in availability),
index=0,
priority=2,
abstol=20,
reltol=0,
name="Cost",
)
# Set up secondary objective: minimize difference in shift length
model.setObjectiveN(maxShift - minShift, index=1, priority=1, name="Fairness")
additional constraint:
Obj 1 \(\leq\) rhs
\[ \begin{align*} \min & ~f_2(x) \\ \text{s. t. } & ~x \in C \\ & ~f_1(x) \leq \epsilon_1 \end{align*} \]
base_value = max(objbnd + |objval| * MIPGap, objbnd + MIPGapAbs, objval)
relaxation = max(ObjNRelTol * |base_value|, ObjNAbsTol)
rhs(eps_1) = base_value + relaxation
# objbnd : best bound of objective Obj 1
# objval : best solution value for objective Obj 1
# MIPGap : relative MIP gap
# MIPGapAbs : absolute MIP gap
# ObjNRelTol: further allowable relative degradation for Obj 1
# ObjNAbsTol: further allowable absolute degradation for Obj 1
“There are 45 coefficients in 2 distinct groups.
Is this a multi-objective case in hiding?”
ModelSense
attribute
Too many objectives?
Weighted: log will be the same as for a single-objective model
Hierarchical:
---------------------------------------------------------------------------
Multi-objectives: starting optimization with 3 objectives ...
---------------------------------------------------------------------------
[...]
---------------------------------------------------------------------------
Multi-objectives: optimize objective 1 (Name) ...
---------------------------------------------------------------------------
Mixed:
---------------------------------------------------------------------------
Multi-objectives: starting optimization with 5 objectives (3 combined) ...
---------------------------------------------------------------------------
[...]
---------------------------------------------------------------------------
Multi-objectives: optimize objective 1 (weighted) ...
---------------------------------------------------------------------------
Idea: One model → solutions to multiple scenarios
Examples:
Motivation to use the Gurobi Multi-Scenario API:
Changes you can make:
It is not possible to explicitly:
0
bounds, then change the boundsGRB.INFINITY
or -GRB.INFINITY
Phase 1: Find the best solution over all scenarios
[...]
Solving a multi-scenario model with 2 scenarios...
[...]
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
H 0 0 1.850017e+09 8.0000e+08 56.8% - 0s
Optimal solution found at node 131289 - now completing multiple scenarios...
Phase 2: Find the best solution for each scenario
Worst Incumbent
: The worst solution found over all scenariosBestBd
: Best possible objective value for any solution that has not yet been foundContinuous variables: multiple equivalent solutions will not be reported as per our definitions.
Parameter settings | Behavior |
---|---|
PoolSearchMode=0 |
Stores all solutions found in the regular optimization No additional tree search performed |
PoolSearchMode=1 PoolSolutions=n |
Stores n-1 additional solutions to the optimal solution Controls how many solutions to save |
PoolSearchMode=2 PoolSolutions=n PoolGap=x |
Stores n-1 best solutions with a MIPGap less than x% in addition to the optimal solution Requires more expensive tree search than PoolSearchMode=1 |
# Limit how many solutions to collect
model.setParam(GRB.Param.PoolSolutions, 100)
# Limit the search space by setting a gap for the
# worst possible solution that will be accepted
model.setParam(GRB.Param.PoolGap, 0.10)
# Do a systematic search for the k best solutions
model.setParam(GRB.Param.PoolSearchMode, 2)
Phase 1: Find one provably optimal solution (identical to MIP log except for last line)
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
0 0 65.87500 0 1 65.00000 65.87500 1.35% - 0s
Optimal solution found at node 0 - now completing solution pool...
Idea:
Examples:
import gurobipy as gp
def mycallback(model, where):
if where == gp.GRB.Callback.MIP:
solcnt = model.cbGet(gp.GRB.Callback.MIP_SOLCNT)
nodecnt = model.cbGet(gp.GRB.Callback.MIP_NODCNT)
if nodecnt >= 10 and solcnt >= 4:
print('Stop early')
model.terminate()
model = gp.read("data/glass4.mps")
model.optimize(mycallback)
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
[...]
0 2 8.0000e+08 0 77 2.0000e+09 8.0000e+08 60.0% - 0s
H 64 79 1.950016e+09 8.0000e+08 59.0% 22.5 0s
Stop early
H 74 79 1.900016e+09 8.0000e+08 57.9% 20.7 0s
[...]
Explored 79 nodes (2138 simplex iterations) in 0.11 seconds (0.08 work units)
[...]
Solution count 5: 1.90002e+09 1.95002e+09 2.00002e+09 ... 3.13336e+09
Solve interrupted
Best objective 1.900016200000e+09, best bound 8.000042222222e+08, gap 57.8949%
User-callback calls 527, time in user-callback 0.00 sec
optimize()
to access it from within:def mycallback(model, where):
...
if mycallback.value == 1:
...
mycallback.value = 1
model.optimize(mycallback)
cb
” (Model.cbGetSolution()
, Model.cbSetSolution()
, Model.cbLazy()
, Model.cbGet()
…) and Model.terminate()
Gurobi Optimizer version 11.0.3 build v11.0.3rc0 (mac64[arm])
[...]
Optimize a model with 14 rows, 72 columns and 72 nonzeros
[...]
Solved in 0 iterations and 0.00 seconds (0.00 work units)
Infeasible model
Why is the model infeasible?
→ Compute an Irreducible Inconsistent Subsystem (IIS)
Given an infeasible system of constraints
Meant to be read and analyzed by a human
Computational complexity
Control IIS computation
Attributes to include (=1
) or exclude (=0
) constraints or bounds from the IIS:
Modified model minimizing the amount by which the bounds and linear constraints of the original model are violated
Violation types (relaxobjtype
):
Infeasible model
\[ \begin{align*} \min &~c^T x \\ \text{s. t. } &~Ax \leq b \\ &~x \geq 0 \end{align*} \]
Feasibility relaxation
model
\[ \begin{align*} \min &||(s, u)||_{\color{red}{p}} \\ \text{s. t. } &~Ax -s \leq b \\ &~x + u \geq 0 \\ &~s, u \geq 0 \end{align*} \]
# Simplified version
model.feasRelaxS(relaxobjtype, minrelax, vrelax, crelax)
# relaxobjtype (integer): 0-norm, 1-norm, 2-norm
# minrelax (boolean): control whether original obj function should be considered
# vrelax (boolean): indicates whether variable bounds can be relaxed
# crelax (boolean): indicates whether constraints can be relaxed
# Full version
model.feasRelax(relaxobjtype, minrelax, vars, lbpen, ubpen, constrs, rhspen)
Start Values and Variable Hints
→ Complete or partial solutions provided by the user
Start Heuristics
→ Specialized heuristics in Gurobi to produce initial solutions
Situation:
Idea: Reduce solve times by specifying these values
Example: Planning with rolling horizon: every month plan the next 12 months
→ values from prior solution of previous months can be used as start or hints
Start Values | Variable Hints |
---|---|
Generate initial integer solution, which is improved via MIP search | Guide MIP search toward anticipated values, affects entire solution process |
Can specify partial solution, to be completed by solver | Can specify hints for subset of variables, to be used by solver |
Use Start variable attribute (or load an .MST MIP start file) |
Use VarHintVal variable attribute. Optional confidence by VarHintPri |
Supports multiple start values via NumStart model attribute and StartNumber |
Supports only one hint per variable |
NoRelHeurTime
/ NoRelHeurWork
parametersZeroObjNodes
MinRelNodes
PumpPasses
1. Explainer for ill-conditioned LPs:
kappa-explain
: Provides a row- or column-based ill-conditioning explanationangle-explain
: Finds pairwise explanations for ill-conditioning2. Feasibility checks for solution files:
Installation: python -m pip install gurobi-modelanalyzer
Basic usage:
afiro
kappa_explain()
will generate a new LP or MPS file, containing the ill-conditioning certificate:
afiro_kappaexplain.lp
:
Minimize
0 X36 + 0 X04 + 0 X15 + 0 X16 + 0 X26 + 0 X38 + 0 X37
Subject To
GRB_Combined_Row: 0.0303868836044176 X23 + 4.80518e-10 X01
- 4.65661e-10 X03 = 0
(mult=2696322.968477607)R09x: - 0.9999999000000001 X01 + X03 = 0
(mult=-2696322.6896988587)R09: - X01 + X03 = 0
(mult=0.2787787486643817)X46: - X03 + 0.109 X22 <= 0
(mult=0.030386883604417606)R19: X23 - X22 + X24 + X25 = 0
(mult=0.030386883604417606)X45: - X25 <= 0
(mult=0.030386883604417606)X48: 0.301 X01 - X24 <= 0
Bounds
End
afiro
angle_explain()
returns a list of tuples:
feasRelax()
and computeIIS()
©️ Gurobi Optimization – gurobi.github.io/slides/