Lecture 2 — Mathematical Programming with Python and Gurobi

Exact solvers as baselines and evaluation tools

1 Learning Objectives

By the end of this lecture, students should be able to:

  • understand the role of mathematical programming solvers in optimization
  • explain the architecture of a solver-based modeling workflow
  • install and configure Gurobi with Python
  • formulate and solve LP, ILP, and MILP models using the Gurobi Python API
  • interpret solver output and diagnose basic modeling issues
  • distinguish modeling concerns from algorithmic concerns

2 Lecture Roadmap

This lecture proceeds in six blocks:

  1. the role of exact solvers in optimization workflows
  2. Gurobi + Python setup and verification
  3. the core Gurobi API modeling pattern
  4. worked LP, ILP, and MILP examples
  5. status-aware solution handling and debugging
  6. implementation best practices for reliable experiments

3 Optimization Models and Solvers

Mathematical optimization models such as LPs, ILPs, and MILPs are abstract mathematical formulations that precisely describe a decision problem in terms of variables, objectives, and constraints. By themselves, these models do not produce solutions. To compute optimal or near-optimal decisions in practice, a model must be processed by a solver.

A solver is responsible for transforming the abstract mathematical description into concrete algorithmic steps that explore the feasible region, evaluate candidate solutions, and certify optimality or infeasibility.

A modern optimization workflow therefore consists of two conceptually distinct but tightly connected layers:

  • Modeling layer The decision problem is formulated mathematically: decision variables are defined, objectives are specified, and constraints are expressed. At this stage, the focus is on correctness, clarity, and faithful representation of the real-world problem.

  • Solver layer The mathematical model is processed by algorithmic machinery that searches for optimal solutions. This layer is concerned with computational efficiency, numerical stability, and convergence guarantees.

This separation of concerns is fundamental. It allows the same mathematical model to be solved using different algorithms or solvers, and enables the same solver to be applied across a wide range of application domains without changing the underlying algorithms.


3.1 What Is a Solver?

An optimization solver is a complex software system that automates the solution of mathematical optimization problems. At a high level, a solver:

  • accepts a formal mathematical model as input, including variables, objective functions, and constraints,
  • applies sophisticated algorithmic techniques such as simplex, interior-point methods, branch-and-bound, branch-and-cut, and cutting-plane methods, and
  • returns a solution together with detailed information about feasibility, optimality, bounds, and convergence status.

Beyond computing solutions, modern solvers also provide diagnostic tools for detecting infeasibility, numerical issues, or modeling errors.

Well-known optimization solvers include commercial solvers such as Gurobi and CPLEX, as well as open-source solvers such as GLPK and CBC. These solvers differ in performance, supported features, and licensing, but they all implement the same underlying mathematical programming paradigms.

In this course, the primary solver used will be Gurobi, selected for its state-of-the-art performance, robustness, and comprehensive support for Python-based modeling.


3.2 Gurobi Overview

Gurobi is a commercial-grade optimization solver designed for solving linear, integer, and mixed-integer programming problems at scale. It is widely regarded as one of the fastest and most reliable solvers available for MILP.

Key characteristics of Gurobi include:

  • full support for LP, ILP, and MILP formulations,
  • advanced presolve routines that simplify models before optimization,
  • powerful cutting-plane strategies that strengthen LP relaxations,
  • parallel branch-and-bound algorithms that exploit modern multi-core architectures, and
  • well-designed application programming interfaces (APIs) for Python, C++, Java, and other languages.

Gurobi is extensively used in industry for large-scale planning and optimization problems, and it is also widely adopted in academic research. Academic licenses are available free of charge, making it suitable for teaching and research purposes.


3.3 Python as a Modeling Language

In this course, Python is used as the primary modeling language for mathematical programming. Rather than writing models in a dedicated algebraic modeling language, optimization models are constructed programmatically using Python objects, expressions, and control structures.

Python-based modeling offers several important advantages:

  • seamless integration with data processing and scientific computing libraries such as NumPy and pandas,
  • easy automation of experiments, parameter studies, and batch runs,
  • natural integration with heuristics and metaheuristics implemented in Python, and
  • improved reproducibility, readability, and extensibility of optimization code.

Within this setup, Python acts as a high-level interface that expresses the optimization model, while the computationally intensive solution process is handled by Gurobi’s highly optimized solver core.


3.4 Installing and Configuring Gurobi

To use Gurobi with Python, the following steps are required:

  1. Install the Gurobi Optimizer on the system.
  2. Obtain and activate an academic license.
  3. Install the Gurobi Python package (gurobipy).
  4. Verify that the installation is correctly configured.

A successful installation can be verified by running the following command in Python:

import gurobipy as gp
print(gp.gurobi.version())

If this command executes without errors and prints the Gurobi version information, the solver and its Python interface are correctly installed and ready to use.


4 The Gurobi Python API: Core Concepts

The Gurobi Python API provides a programmatic interface for building and solving mathematical optimization models. At its core, a Gurobi model is composed of a small number of fundamental objects that together define the optimization problem.

The main building blocks are:

  • Model The central container that holds the entire optimization problem, including variables, objective, constraints, and solver parameters.

  • Variables Decision variables with specified domains (continuous, integer, binary), bounds, and optional names. Variables represent the unknown quantities the solver will determine.

  • Objective A linear expression in the decision variables that is either minimized or maximized.

  • Constraints Linear expressions that restrict the feasible values of the decision variables and encode the logical, physical, or resource limitations of the problem.

These objects interact through a well-defined modeling workflow that is largely independent of the specific problem being solved.


4.1 Typical Modeling Pattern

Most optimization models built with Gurobi follow the same high-level sequence:

  1. Create a model to serve as the container for all components.
  2. Add decision variables, specifying bounds and variable types.
  3. Set the objective function, indicating whether it is a minimization or maximization problem.
  4. Add constraints that define feasibility.
  5. Optimize the model by invoking the solver.
  6. Inspect the solution, including variable values, objective value, and solver status.

This pattern applies uniformly to LPs, ILPs, and MILPs.


4.2 From Math to Code: A Reusable Skeleton

A reliable way to avoid implementation errors is to keep a fixed code structure and only replace data, variables, objective, and constraints for each new model.

import gurobipy as gp
from gurobipy import GRB

def solve_template(cost, A, b):
    m = gp.Model("template")
    m.Params.OutputFlag = 0  # keep logs quiet for scripted runs

    n = len(cost)
    x = m.addVars(n, lb=0.0, name="x")

    m.setObjective(gp.quicksum(cost[j] * x[j] for j in range(n)), GRB.MINIMIZE)
    for i, row in enumerate(A):
        m.addConstr(gp.quicksum(row[j] * x[j] for j in range(n)) <= b[i], name=f"c_{i}")

    m.optimize()
    return m, x

This template highlights good habits: named constraints, explicit bounds, and centralized solve logic.


4.3 Example 1: Solving a Linear Program

Consider the following linear programming problem:

\[ \begin{aligned} \max\ \ & 3x_1 + 5x_2 \\ \text{s.t.}\ \ & 2x_1 + 4x_2 \le 100, \\ & x_1, x_2 \ge 0. \end{aligned} \]

Python Implementation

import gurobipy as gp
from gurobipy import GRB

model = gp.Model("lp_example")

x1 = model.addVar(lb=0, name="x1")
x2 = model.addVar(lb=0, name="x2")

model.setObjective(3*x1 + 5*x2, GRB.MAXIMIZE)
model.addConstr(2*x1 + 4*x2 <= 100)

model.optimize()

In this code:

  • a model object is created,
  • two continuous nonnegative variables are added,
  • a linear objective is defined, and
  • a single linear constraint is introduced.

Inspecting the Solution

After optimization, solution information can be accessed directly from the model and variables:

print("x1 =", x1.X)
print("x2 =", x2.X)
print("Objective value =", model.ObjVal)

The attributes .X and .ObjVal return the optimal variable values and objective value, respectively, provided that an optimal solution exists.


4.4 Example 2: Integer Programming with Binary Variables

Consider a simple knapsack problem with binary selection decisions:

\[ \begin{aligned} \max\ \ & \sum_j v_j x_j \\ \text{s.t.}\ \ & \sum_j w_j x_j \le W, \\ & x_j \in \{0,1\}. \end{aligned} \]

Here, each item \(j\) is either selected or not, and the total weight must not exceed the capacity \(W\).

Python Implementation

v = [10, 13, 18, 31]
w = [5, 8, 12, 20]
W = 30

model = gp.Model("knapsack")

x = model.addVars(len(v), vtype=GRB.BINARY, name="x")

model.setObjective(gp.quicksum(v[j]*x[j] for j in range(len(v))), GRB.MAXIMIZE)
model.addConstr(gp.quicksum(w[j]*x[j] for j in range(len(w))) <= W)

model.optimize()

This example illustrates how integer variables are introduced via the vtype=GRB.BINARY argument and how summations are conveniently expressed using quicksum.


4.5 Example 3: A Simple MILP with Activation

Consider a production decision with a fixed setup cost and a capacity limit that is active only when production is enabled:

\[ \begin{aligned} \min\ \ & F y + c x \\ \text{s.t.}\ \ & x \le U y, \\ & x \ge 0,\quad y \in \{0,1\}. \end{aligned} \]

Python Implementation

F = 100
c = 2
U = 50

model = gp.Model("milp_example")

x = model.addVar(lb=0, name="x")
y = model.addVar(vtype=GRB.BINARY, name="y")

model.setObjective(F*y + c*x, GRB.MINIMIZE)
model.addConstr(x <= U*y)

model.optimize()

This model demonstrates a typical on–off constraint, where a continuous variable is linked to a binary decision. Such patterns are central to MILP modeling.


4.6 Solver Output and Status Codes

After optimization, Gurobi reports detailed information about the outcome of the solve, including:

  • the solver status (optimal, infeasible, unbounded, or other termination states),
  • the objective value of the best solution found,
  • the primal values of all decision variables, and
  • dual information for LPs, when applicable.

It is good practice to always check the solver status before using solution values:

if model.status == GRB.OPTIMAL:
    print("Optimal solution found")
elif model.status == GRB.INFEASIBLE:
    print("Model is infeasible")

4.8 Modeling Errors and Debugging

Common sources of errors in solver-based modeling include:

  • missing or incorrectly specified constraints,
  • incorrect variable domains (e.g., using continuous variables instead of binaries),
  • overly large big-\(M\) values that weaken the formulation, and
  • mutually contradictory constraints that make the model infeasible.

Gurobi provides diagnostic tools such as Irreducible Infeasible Subsystem (IIS) computation, which helps identify minimal sets of conflicting constraints and is invaluable for debugging complex models.


4.9 Infeasibility Diagnosis Workflow

When a model is infeasible, use a repeatable debugging routine:

  1. verify units and signs in every constraint
  2. check variable bounds for accidental contradictions
  3. compute and inspect IIS
  4. relax or correct the conflicting constraints
  5. rerun on a small test instance first

Minimal IIS extraction code:

if model.status == GRB.INFEASIBLE:
    model.computeIIS()
    model.write("model_iis.ilp")  # inspect this file to see conflicting rows/bounds

4.10 Useful Gurobi Parameters for Experiments

Parameter Purpose Typical classroom use
TimeLimit Stops solve after a fixed time Compare solution quality under strict runtime budgets
MIPGap Accepts near-optimal MILP solutions Use when exact proof is too expensive
Threads Controls CPU parallelism Ensure fair solver comparisons across runs
OutputFlag Enables/disables solver logs Set to 0 in scripts, 1 while debugging

Example:

model.Params.TimeLimit = 60
model.Params.MIPGap = 0.01
model.Params.OutputFlag = 1

4.11 Best Practices for Solver-Based Modeling

Developing robust and efficient optimization models requires both sound mathematical formulation and disciplined implementation practices. The following guidelines help ensure correctness, interpretability, and computational efficiency.

  • Clearly separate data, model, and solution logic. Organize code so that problem data, model construction, and solution analysis are handled in distinct sections or modules. This separation improves readability, facilitates debugging, and makes it easier to modify or extend the model.

  • Start with small test instances. Begin with simplified or reduced-size instances where the expected solution can be reasoned about manually. This helps validate the formulation and identify modeling errors before scaling up to larger or more complex instances.

  • Verify LP relaxations before enforcing integrality. Solve the LP relaxation of an ILP or MILP to check feasibility, constraint logic, and objective behavior. A correct and well-behaved LP relaxation is a strong indicator that the integer model is formulated properly.

  • Always inspect solver status before using results. Never assume that a solver call returns an optimal solution. Check the solver status to confirm optimality or identify infeasibility, unboundedness, or early termination before interpreting variable values or objective outcomes.


4.12 Takeaways

  • Solvers are algorithmic engines that turn mathematical models into solutions
  • Python provides a flexible and powerful modeling environment
  • Gurobi supports LP, ILP, and MILP through a unified API
  • Careful modeling is as important as solver choice

5 Mini Exercises

  1. Extending a linear program with an additional resource constraint. Starting from the LP example solved earlier, introduce a second resource constraint (for example, a limit on raw material or machine time).

    • Formulate the new constraint mathematically.
    • Update the Python implementation accordingly.
    • Solve the modified model and compare the new optimal solution with the original one. Question: How does the additional constraint change the feasible region and the optimal solution?
  2. Knapsack with a cardinality constraint. Extend the knapsack model by limiting the number of items that can be selected.

    • Introduce a parameter \(p\) representing the maximum number of items that may be chosen.
    • Add a cardinality constraint of the form \(\sum_j x_j \le p\).
    • Solve the resulting ILP and analyze how the optimal solution differs from the unconstrained knapsack. Question: Under what conditions does the cardinality constraint become binding?
  3. Replacing big-\(M\) with an indicator constraint. Consider the MILP example with a setup decision and production variable.

    • Reformulate the constraint \(x \le U y\) using an indicator constraint of the form “\(y = 0 \Rightarrow x = 0\)”.
    • Implement the indicator-based formulation in Gurobi’s Python API.
    • Solve both formulations and compare solver behavior. Question: Do you observe any differences in model clarity, numerical stability, or solution time?
  4. Diagnosing infeasibility with IIS. Construct a deliberately inconsistent model (for example, add both \(x \le 5\) and \(x \ge 10\) with tight bounds), solve it, and run IIS extraction.

    • Print the returned status and verify that it is infeasible.
    • Export the IIS file and inspect which constraints appear in it.
    • Fix the inconsistency and re-solve the corrected model. Question: Which modeling mistake caused infeasibility, and how quickly did IIS reveal it?

These exercises are designed to reinforce both modeling concepts and practical solver usage, and to highlight how small formulation changes can significantly affect model behavior.


6 References for This Chapter

  1. Gurobi Optimization, LLC. (2026). Gurobi Optimizer Reference Manual. URL: https://docs.gurobi.com/projects/optimizer/en/current/index.html
  2. Gurobi Optimization, LLC. (2026). Python API Reference. URL: https://docs.gurobi.com/projects/optimizer/en/current/reference/python.html
  3. Gurobi Optimization, LLC. (2026). Parameter Reference. URL: https://docs.gurobi.com/projects/optimizer/en/current/reference/parameters.html
  4. Gurobi Optimization, LLC. (2026). Example Tour. URL: https://docs.gurobi.com/projects/examples/en/current/overview/examplelist.html
  5. Achterberg, T. (2009). SCIP: Solving constraint integer programs. Mathematical Programming Computation, 1, 1-41. DOI: 10.1007/s12532-008-0001-1
  6. Bixby, R. E. (2012). A brief history of linear and mixed-integer programming computation. In Documenta Mathematica, Extra Volume ISMP (pp. 107-121). DOI: 10.4171/DMS/6/16
  7. Land, A. H., & Doig, A. G. (1960). An automatic method of solving discrete programming problems. Econometrica, 28(3), 497-520. DOI: 10.2307/1910129
Back to top