Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation
Back to Writing

Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation

Michael BrenndoerferNovember 23, 202553 min read12,369 wordsInteractive

Complete guide to Mixed Integer Linear Programming (MILP) for factory optimization, covering mathematical foundations, constraint modeling, branch-and-bound algorithms, and practical implementation with Google OR-Tools. Learn how to optimize production planning with discrete setup decisions and continuous quantities.

Data Science Handbook Cover
Part of Data Science Handbook

This article is part of the free-to-read Data Science Handbook

View full handbook
Reading Level

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Mixed Integer Linear Programming (MILP) for Factory Optimization

Mixed Integer Linear Programming (MILP) is a powerful optimization technique that combines the flexibility of linear programming with the discrete decision-making capabilities of integer programming. In factory optimization, MILP allows us to model complex production scenarios where we need to make discrete choices (like which machines to use, which products to produce) while optimizing continuous variables (like production quantities, resource allocation). This makes MILP particularly valuable for real-world manufacturing problems where decisions are inherently discrete but quantities are continuous.

Unlike pure linear programming, which assumes all variables can take any fractional value, MILP recognizes that many real-world decisions are binary (yes/no) or integer-valued (whole numbers of items). For example, we can't produce 2.3 machines or hire half a worker. This discrete nature makes MILP more realistic for factory optimization while maintaining the computational advantages of linear programming for the continuous aspects of the problem.

The key advantage of MILP in factory settings is its ability to handle both tactical decisions (what to produce) and strategic decisions (capacity investments, technology choices) in a single optimization framework. This integrated approach allows factory managers to optimize not just production schedules but also long-term capacity planning and resource allocation decisions.

Advantages

MILP provides several significant advantages for factory optimization. First, it offers mathematical rigor and guarantees optimal solutions (within computational limits), which is important for high-stakes manufacturing decisions where suboptimal choices can cost millions. The linear programming foundation ensures that the continuous aspects of production (quantities, resource usage) are optimized efficiently, while the integer constraints handle the discrete nature of real-world manufacturing decisions.

Second, MILP models are highly flexible and can incorporate a wide range of constraints and objectives. We can model complex relationships between products, machines, and resources, including setup costs, capacity constraints, demand requirements, and even multi-period planning horizons. This flexibility makes MILP suitable for diverse manufacturing scenarios, from simple production scheduling to complex supply chain optimization.

Third, MILP provides clear sensitivity analysis capabilities, allowing managers to understand how changes in parameters (demand, costs, capacities) affect optimal solutions. This insight is invaluable for scenario planning and risk management in manufacturing environments where conditions frequently change.

Disadvantages

Despite its power, MILP has several limitations that factory managers should consider. The most significant disadvantage is computational complexity. As the number of integer variables increases, the problem becomes exponentially harder to solve. This means that large-scale factory optimization problems may require significant computational resources and time, potentially making real-time optimization impractical for very complex scenarios.

Second, MILP requires precise mathematical modeling, which can be challenging for complex manufacturing systems. Translating real-world factory constraints into mathematical formulations requires deep understanding of both the manufacturing process and optimization theory. Poor modeling can lead to solutions that are mathematically optimal but practically infeasible.

Third, MILP assumes linear relationships between variables, which may not always reflect real-world manufacturing dynamics. While many factory optimization problems can be approximated linearly, some relationships (like economies of scale or learning curves) are inherently nonlinear and may require more complex modeling approaches or approximations.

Formula

Imagine you're managing a factory floor. Your job is to decide what to produce and how much, all while keeping costs low and staying within machine capacities. But here's the catch: some of your decisions are straightforward quantities (produce 5 units, use 12 hours), while others are all-or-nothing choices (set up the machine or don't, produce this product line or skip it). This fundamental difference between continuous quantities and discrete yes/no decisions is what makes factory optimization both challenging and interesting.

The mathematical challenge emerges because these two types of decisions interact in complex ways. You can't produce anything without first setting up the machine, but setup costs money regardless of how much you produce. Machine capacity limits how much you can make, but the capacity itself depends on which machines you've decided to set up. To solve this problem mathematically, we need a framework that can handle both continuous optimization (finding the best quantities) and discrete decision-making (choosing which setups to perform) simultaneously.

This is exactly what Mixed Integer Linear Programming provides. The "mixed" part means we combine continuous variables (for quantities) with integer or binary variables (for discrete choices). The "linear" part means all relationships are linear, making the problem computationally tractable. The "programming" part refers to optimization, finding the best solution among all possibilities.

Let's build this formulation step by step, starting with what we're trying to optimize.

Building the Objective Function: What Are We Optimizing?

Every optimization problem starts with a clear goal. In factory optimization, that goal is typically to minimize total cost. But here's where it gets interesting: manufacturing costs don't all behave the same way.

Think about what happens when you produce more units. Some costs increase proportionally: raw materials, direct labor, energy consumption. If you produce 10 units instead of 5, these costs roughly double. These are variable costs, which vary directly with production quantity.

But other costs don't change at all with quantity. Setting up a machine costs the same whether you produce 1 unit or 1000 units. Deciding to produce a product line requires the same initial investment regardless of volume. These are fixed costs, which are incurred once when you make a decision, then remain constant.

This creates a fundamental economic trade-off. If setup costs are high, you want to minimize setups, which might mean producing larger batches of fewer products. If variable costs dominate, you want to produce exactly what's needed. The optimizer needs to balance these competing forces.

To model this mathematically, we need two separate terms in our objective function:

mini=1ncixi+j=1mdjyj\min \quad \sum_{i=1}^{n} c_i x_i + \sum_{j=1}^{m} d_j y_j

Let's break down what each part means:

The Variable Cost Term: i=1ncixi\sum_{i=1}^{n} c_i x_i

  • nn: number of products or continuous activities we can produce
  • i{1,,n}i \in \{1, \ldots, n\}: index running through all products
  • xix_i: how many units of product ii to produce (a continuous variable, xi0x_i \geq 0)
  • cic_i: variable cost per unit of product ii (e.g., cost of materials and labor per unit)

This summation captures all costs that scale with production. If c_1 = \5perunitandweproduceper unit and we producex_1 = 10units,thecostcontributionisunits, the cost contribution is$5 \times 10 = $50$. The more we produce, the higher this cost becomes.

The Fixed Cost Term: j=1mdjyj\sum_{j=1}^{m} d_j y_j

  • mm: number of discrete setup decisions we can make
  • j{1,,m}j \in \{1, \ldots, m\}: index running through all setup decisions
  • yjy_j: binary variable indicating whether we make setup decision jj (yj=1y_j = 1 if yes, yj=0y_j = 0 if no)
  • djd_j: fixed cost incurred when we make decision jj (setup cost)

This summation captures all costs that are triggered by discrete decisions. If d_1 = \30andwedecidetosetup( and we decide to set up (y_1 = 1),wepay), we pay $30regardlessofhowmuchweproduce.Ifwedontsetup( regardless of how much we produce. If we don't set up (y_1 = 0$), we pay nothing.

Why This Structure Works

The optimizer can now make two types of decisions simultaneously:

  1. Continuous decisions (xix_i values): How much of each product to produce
  2. Discrete decisions (yjy_j values): Which setups to perform

The objective function automatically balances these decisions. If a product has high variable costs but low setup costs, the optimizer might produce small quantities. If setup costs are high but variable costs are low, it might produce large batches to amortize the setup cost. The mathematical formulation captures this economic logic naturally.

Capturing Resource Constraints: What Limits Can We Work With?

Minimizing cost is only half the story. In the real world, we face physical and operational limits that constrain what we can actually do. Machines have finite capacity. Materials are available in limited quantities. We may have contractual obligations to meet certain demand levels. These constraints define the feasible region, the set of all production plans that are physically possible given our resources.

The mathematical challenge is that resource consumption comes from two different sources, and we need to model both:

  1. Variable resource consumption from production quantities: The more we produce, the more resources we use.
  2. Fixed resource consumption from setup decisions: Setting up a machine consumes resources regardless of production volume.

Modeling Variable Consumption

When we produce xix_i units of product ii, we consume resources proportionally. If each unit of Product A requires 2 hours on Machine 1, then producing 5 units requires 2×5=102 \times 5 = 10 hours. This is straightforward linear scaling.

Modeling Fixed Consumption

But discrete decisions also consume resources. Setting up Machine 1 might require 1 hour of technician time, whether we produce 1 unit or 100 units. Switching production lines might consume a fixed amount of cleaning material. These fixed consumptions happen once when we make the decision, then don't scale with production.

Combining Both Types of Consumption

For each resource jj (machine, material, etc.), we need to ensure that total consumption doesn't exceed available capacity uju_j. The consumption comes from both sources, so we write:

i=1naijxi+k=1pbkjykujj{1,,m}\sum_{i=1}^{n} a_{ij} x_i + \sum_{k=1}^{p} b_{kj} y_k \leq u_j \quad \forall j \in \{1, \ldots, m\}

Let's understand each component:

  • j{1,,m}j \in \{1, \ldots, m\}: index for each resource (Machine 1, Machine 2, Material A, etc.)
  • aija_{ij}: amount of resource jj consumed per unit of product ii (e.g., hours per unit)
  • xix_i: production quantity of product ii (from our objective function)
  • bkjb_{kj}: fixed amount of resource jj consumed when we make setup decision kk
  • yky_k: binary variable for setup decision kk (1 if we set up, 0 if we don't)
  • uju_j: total capacity or availability of resource jj
  • j\forall j: this constraint should hold for every resource

Why We Need Both Terms: A Concrete Example

Consider Machine 1 with 20 hours of capacity. Product A requires 2 hours per unit, and setting up for Product A requires 1 hour.

In this example:

  • x1x_1: number of units of Product A to produce (continuous variable, x10x_1 \geq 0)
  • y1y_1: binary variable indicating whether to set up for Product A (y1=1y_1 = 1 if we set up, y1=0y_1 = 0 if we don't)
  • a11=2a_{11} = 2: hours of Machine 1 required per unit of Product A
  • b11=1b_{11} = 1: fixed hours of Machine 1 required when setting up for Product A
  • u1=20u_1 = 20: total capacity of Machine 1 in hours

The constraint for Machine 1 becomes:

a11x1+b11y1u1a_{11} x_1 + b_{11} y_1 \leq u_1

Substituting the values:

2x1+1y1202x_1 + 1 \cdot y_1 \leq 20

Example calculation: If we decide to set up (y1=1y_1 = 1) and produce x1=10x_1 = 10 units:

  • Variable consumption: a11x1=2×10=20a_{11} x_1 = 2 \times 10 = 20 hours
  • Fixed consumption: b11y1=1×1=1b_{11} y_1 = 1 \times 1 = 1 hour
  • Total consumption: 20+1=2120 + 1 = 21 hours

Since 21>2021 > 20, this violates the capacity constraint. The optimizer should either reduce production (e.g., produce 9 units: 2(9)+1(1)=18+1=19202(9) + 1(1) = 18 + 1 = 19 \leq 20) or skip the setup (produce 10 units without setup: 2(10)+1(0)=20+0=20202(10) + 1(0) = 20 + 0 = 20 \leq 20).

This dual structure, combining variable consumption from production with fixed consumption from setups, is what makes MILP constraints powerful. They capture the reality that both "how much" and "whether to" decisions consume resources.

Notational Note

You might notice that binary variables are indexed differently in the objective (yjy_j) versus constraints (yky_k). This is a common convention: jj in the objective refers to setup decisions, while jj in constraints refers to resources. When the number of setup decisions equals the number of resources, yjy_j and yky_k refer to the same variables in different contexts.

Defining Variable Types: Why Two Types Matter

The mathematical power of MILP comes from its ability to handle two fundamentally different types of variables in a single optimization problem. Understanding why we need both types, and how they differ, is crucial for grasping how MILP works.

Continuous Variables: xi0x_i \geq 0

Continuous variables xix_i can take any non-negative real value. They represent quantities that can be measured with arbitrary precision: production volumes (3.7 units), resource allocations (12.5 hours), time durations (45.2% of capacity).

We require xi0x_i \geq 0 because negative production doesn't make physical sense. You can't produce -5 units of a product. The non-negativity constraint ensures all solutions are physically meaningful.

Binary Variables: yk{0,1}y_k \in \{0, 1\}

Binary variables yky_k are much more restrictive: they can only be 0 or 1. There's no middle ground. When yk=1y_k = 1, we make decision kk (set up the machine, produce the product line, invest in the technology). When yk=0y_k = 0, we don't make that decision.

This binary nature is what makes the problem "mixed integer." We're mixing continuous optimization (finding optimal quantities) with discrete decision-making (choosing which setups to perform).

Why This Constraint Is Profound

The constraint yk{0,1}y_k \in \{0, 1\} might look simple, but it has important implications. It prevents fractional setups: you cannot set up a machine "halfway" or produce a product line "75% of the way." Either you do it completely (yk=1y_k = 1) or you don't do it at all (yk=0y_k = 0).

This discrete constraint fundamentally changes the geometry of the feasible region. In pure linear programming, the feasible region is a convex polyhedron, a smooth continuous shape. But when we add integer constraints, the feasible region becomes the intersection of that polyhedron with discrete lattice points. This creates a non-convex feasible region, which is why MILP problems are computationally challenging but mathematically precise for real-world factory decisions.

The "mixed" nature means we get the best of both worlds: the efficiency of continuous optimization for quantities, combined with the precision of discrete decision-making for yes/no choices.

The Complete Formulation: Bringing It All Together

Now we can assemble the complete MILP formulation by combining the objective function with all constraints:

mini=1ncixi+j=1mdjyjsubject toi=1naijxi+k=1pbkjykujj{1,,m}xi0i{1,,n}yk{0,1}k{1,,p}\begin{aligned} \min \quad & \sum_{i=1}^{n} c_i x_i + \sum_{j=1}^{m} d_j y_j \\ \text{subject to} \quad & \sum_{i=1}^{n} a_{ij} x_i + \sum_{k=1}^{p} b_{kj} y_k \leq u_j \quad \forall j \in \{1, \ldots, m\} \\ & x_i \geq 0 \quad \forall i \in \{1, \ldots, n\} \\ & y_k \in \{0, 1\} \quad \forall k \in \{1, \ldots, p\} \end{aligned}

What This Formulation Captures

This mathematical structure captures the essence of factory optimization:

  1. The objective function minimizes total cost by balancing variable costs (that scale with production) and fixed costs (that are triggered by discrete decisions).

  2. The resource constraints ensure that for each resource, the total consumption from both production activities and setup decisions doesn't exceed available capacity.

  3. The variable constraints ensure all solutions are physically meaningful: production quantities are non-negative, and setup decisions are binary (yes or no).

  4. The optimizer's task is to find values for all xix_i (production quantities) and yky_k (setup decisions) that satisfy all constraints while achieving the minimum possible cost.

This formulation is powerful because it models the real-world interaction between continuous and discrete decisions in a mathematically precise way. The optimizer doesn't just find the best production quantities. It simultaneously determines which setups to perform, recognizing that these decisions are interdependent.

Matrix Notation: The Computational Perspective

While summation notation clearly shows the structure of the problem, computational solvers work more efficiently with matrix notation. This compact representation groups all variables and constraints into vectors and matrices, enabling efficient linear algebra operations:

mincTx+dTysubject toAx+Byux0y{0,1}p\begin{aligned} \min \quad & \mathbf{c}^T \mathbf{x} + \mathbf{d}^T \mathbf{y} \\ \text{subject to} \quad & \mathbf{A}\mathbf{x} + \mathbf{B}\mathbf{y} \leq \mathbf{u} \\ & \mathbf{x} \geq \mathbf{0} \\ & \mathbf{y} \in \{0, 1\}^p \end{aligned}

Understanding the Matrix Components

  • xRn\mathbf{x} \in \mathbb{R}^n: vector of continuous variables (production quantities for all nn products), where xix_i is the quantity of product ii
  • y{0,1}p\mathbf{y} \in \{0, 1\}^p: vector of binary variables (setup decisions for all pp choices), where yky_k indicates whether setup decision kk is made
  • cRn\mathbf{c} \in \mathbb{R}^n: vector of variable cost coefficients (cost per unit for each product), where cic_i is the cost per unit of product ii
  • dRp\mathbf{d} \in \mathbb{R}^p: vector of fixed cost coefficients (setup cost for each decision), where dkd_k is the fixed cost when setup decision kk is made
  • ARm×n\mathbf{A} \in \mathbb{R}^{m \times n}: constraint matrix mapping continuous variables to resources, where row jj corresponds to resource jj and column ii corresponds to product ii. Element Aji=aijA_{ji} = a_{ij} represents the amount of resource jj consumed per unit of product ii
  • BRm×p\mathbf{B} \in \mathbb{R}^{m \times p}: constraint matrix mapping binary variables to resources, where row jj corresponds to resource jj and column kk corresponds to setup decision kk. Element Bjk=bkjB_{jk} = b_{kj} represents the fixed amount of resource jj consumed when setup decision kk is made
  • uRm\mathbf{u} \in \mathbb{R}^m: vector of resource capacities (upper bounds for all mm resources), where uju_j is the capacity of resource jj

Why Matrix Notation Matters

The matrix form is computationally efficient because solvers can use optimized linear algebra libraries to explore the feasible region and identify optimal solutions. Matrix-vector operations are highly optimized in modern computing environments, making this representation much faster than working with individual summations.

This notation also makes problem scaling transparent: adding more products increases the size of x\mathbf{x} and the number of columns in A\mathbf{A}, while adding more setup decisions increases the size of y\mathbf{y} and the number of columns in B\mathbf{B}. Understanding this structure helps you anticipate how problem complexity grows with scale.

Let's visualize the structure of a MILP problem to understand how the mathematical components map to factory decisions:

Out[2]:
Visualization
Notebook output

Mathematical Properties: Why MILP Is Both Powerful and Challenging

Understanding the mathematical structure of MILP problems helps explain both their power and their computational challenges. The geometry of the feasible region reveals why MILP is fundamentally different from pure linear programming.

The Geometry of Feasible Regions

The feasible region of a MILP problem has a distinctive geometric structure. Imagine first the continuous relaxation, what happens if we ignore the integer constraints and allow fractional values. This creates a polyhedron: a geometric shape with flat faces, straight edges, and sharp corners. This is the feasible region of the underlying linear program.

Now impose the integer constraints. We're no longer allowed to use any point in the polyhedron, only the integer lattice points (points with integer coordinates) that lie within it. The feasible region becomes the intersection of the polyhedron with these discrete points.

This intersection creates a non-convex feasible region, which fundamentally distinguishes MILP from pure linear programming.

Why Non-Convexity Matters

In linear programming, the feasible region is convex, which guarantees that any local optimum is also a global optimum. This property enables efficient algorithms like the simplex method that can quickly find optimal solutions by moving along edges of the polyhedron.

In MILP, the non-convexity means we cannot simply follow a gradient or use simplex-like methods. We need to explore discrete points in the feasible region, which is why MILP problems are NP-hard in general. As the number of binary variables grows, the number of potential solutions grows exponentially. With pp binary variables, there are 2p2^p possible combinations of setup decisions to consider. This exponential growth makes exact solution increasingly difficult as problems scale.

The Silver Lining: Extreme Point Property

Despite this complexity, MILP problems have an important property that makes them solvable: if an optimal solution exists, there exists an optimal solution at an extreme point of the continuous relaxation. Extreme points are the "corners" of the polyhedron, points that can't be expressed as a convex combination of other feasible points.

This means we don't need to search through all 2p2^p possible integer combinations. We can focus our search on the corners of the feasible polyhedron where integer solutions are most likely to be found. This property is what makes branch-and-bound algorithms effective.

How Branch-and-Bound Exploits This Structure

Branch-and-bound algorithms exploit the extreme point property by solving a series of linear programming relaxations. They systematically explore extreme points while using bounds to prune away regions that cannot contain optimal solutions. The algorithm:

  1. Starts with the continuous relaxation (ignoring integer constraints)
  2. If the solution has fractional values, branches by fixing variables to integer values
  3. Uses bounds from the LP relaxation to eliminate subproblems that can't be optimal
  4. Continues until an integer solution is found and proven optimal

The Role of Duality and Bounds

Duality theory from linear programming extends to MILP through Lagrangian relaxation, where we relax some constraints and incorporate them into the objective function with penalty terms. This approach provides bounds on the optimal solution value, which helps branch-and-bound algorithms determine when they've found a solution that's close enough to optimal.

These bounds enable more efficient solution algorithms by allowing early termination when the gap between the best known solution and the best possible solution becomes sufficiently small. For large problems where exact solution is computationally infeasible, this allows us to find high-quality solutions within acceptable time limits.

Visualizing Mixed Integer Linear Programming

Let's create visualizations to understand how MILP works in practice. We'll examine a simple factory optimization problem with two products and capacity constraints.

Out[3]:
Visualization
Notebook output

Continuous linear programming feasible region showing all allowed solutions. The shaded area represents all combinations of Product 1 and Product 2 that satisfy both machine capacity constraints. The blue line represents Machine A's constraint (2x₁ + 3x₂ ≤ 12), while the red line represents Machine B's constraint (4x₁ + 2x₂ ≤ 16). Corner points are marked, with the optimal solution at (3, 2) where both constraints are binding.

Notebook output

Mixed integer linear programming feasible points restricted to integer values only. While the continuous LP allows any point within the shaded region, MILP restricts solutions to discrete integer coordinates. Each red dot represents a feasible integer solution where both x₁ and x₂ are whole numbers satisfying the capacity constraints. The optimal integer solution (3, 2) coincides with the continuous optimal solution in this case.

Integer feasible solutions and their profits:
(0, 0): Profit = 0
(0, 1): Profit = 2
(0, 2): Profit = 4
(0, 3): Profit = 6
(0, 4): Profit = 8
(1, 0): Profit = 3
(1, 1): Profit = 5
(1, 2): Profit = 7
(1, 3): Profit = 9
(2, 0): Profit = 6
(2, 1): Profit = 8
(2, 2): Profit = 10
(3, 0): Profit = 9
(3, 1): Profit = 11
(3, 2): Profit = 13
(4, 0): Profit = 12

Now let's visualize the branch-and-bound process that MILP solvers use:

Out[4]:
Visualization
Notebook output

Example

Let's work through a concrete factory optimization example step by step. Consider a factory that produces two products using two machines with the following specifications:

Problem Setup:

  • Product 1: requires 2 hours on Machine A, 4 hours on Machine B, profit of $3 per unit
  • Product 2: requires 3 hours on Machine A, 2 hours on Machine B, profit of $2 per unit
  • Machine A: 12 hours available
  • Machine B: 16 hours available
  • Additional constraint: We can only produce products in whole units (integer constraint)

Step 1: Define Variables

Let x1x_1 = number of units of Product 1 to produce Let x2x_2 = number of units of Product 2 to produce

Step 2: Formulate the Problem

Our objective is to maximize profit: max3x1+2x2\max 3x_1 + 2x_2

Subject to constraints:

  • Machine A capacity: 2x1+3x2122x_1 + 3x_2 \leq 12
  • Machine B capacity: 4x1+2x2164x_1 + 2x_2 \leq 16
  • Non-negativity: x1,x20x_1, x_2 \geq 0
  • Integer constraint: x1,x2Zx_1, x_2 \in \mathbb{Z}

Step 3: Solve the LP Relaxation First

If we ignore the integer constraint, we can solve this as a linear program. We need to find where the constraint boundaries intersect, which gives us the optimal solution.

From Machine A constraint: 2x1+3x2122x_1 + 3x_2 \leq 12, we can express x2x_2 as:

x2122x13x_2 \leq \frac{12 - 2x_1}{3}

From Machine B constraint: 4x1+2x2164x_1 + 2x_2 \leq 16, we can express x2x_2 as:

x2164x12=82x1x_2 \leq \frac{16 - 4x_1}{2} = 8 - 2x_1

To find the intersection point where both constraints are binding (satisfied with equality), we set the two expressions equal:

122x13=82x1\frac{12 - 2x_1}{3} = 8 - 2x_1

Multiplying both sides by 3 to eliminate the denominator:

122x1=3(82x1)12 - 2x_1 = 3(8 - 2x_1)

Expanding the right side:

122x1=246x112 - 2x_1 = 24 - 6x_1

Rearranging terms to collect x1x_1 on one side:

2x1+6x1=2412-2x_1 + 6x_1 = 24 - 12

Simplifying:

4x1=124x_1 = 12

Solving for x1x_1:

x1=124=3x_1 = \frac{12}{4} = 3

Substituting x1=3x_1 = 3 back into the Machine A constraint (with equality) to find x2x_2:

2(3)+3x2=122(3) + 3x_2 = 12

Simplifying:

6+3x2=126 + 3x_2 = 12

Solving for x2x_2:

3x2=126=63x_2 = 12 - 6 = 6 x2=63=2x_2 = \frac{6}{3} = 2

Verification: Let's verify this solution satisfies both constraints:

  • Machine A: 2(3)+3(2)=6+6=12122(3) + 3(2) = 6 + 6 = 12 \leq 12
  • Machine B: 4(3)+2(2)=12+4=16164(3) + 2(2) = 12 + 4 = 16 \leq 16

So the LP relaxation gives us x1=3,x2=2x_1 = 3, x_2 = 2 with profit:

Profit=3x1+2x2=3(3)+2(2)=9+4=13\text{Profit} = 3x_1 + 2x_2 = 3(3) + 2(2) = 9 + 4 = 13

The optimal profit is \13$.

Step 4: Apply Branch-and-Bound

Since we need integer solutions, we use branch-and-bound:

Branch 1: x13x_1 \leq 3

  • New constraint: x13x_1 \leq 3
  • With x1=3x_1 = 3 fixed, from Machine A: 2(3)+3x2122(3) + 3x_2 \leq 12, so 3x2126=63x_2 \leq 12 - 6 = 6, giving x22x_2 \leq 2
  • From Machine B: 4(3)+2x2164(3) + 2x_2 \leq 16, so 2x21612=42x_2 \leq 16 - 12 = 4, giving x22x_2 \leq 2
  • The binding constraint is x22x_2 \leq 2, so the solution is x1=3,x2=2x_1 = 3, x_2 = 2 (this is already an integer solution)
  • Profit = 3(3)+2(2)=9+4=133(3) + 2(2) = 9 + 4 = 13

Branch 2: x14x_1 \geq 4

  • New constraint: x14x_1 \geq 4
  • From Machine B: 4x1+2x2164x_1 + 2x_2 \leq 16, with x1=4x_1 = 4: 4(4)+2x2164(4) + 2x_2 \leq 16
  • Simplifying: 16+2x21616 + 2x_2 \leq 16, so 2x202x_2 \leq 0, giving x20x_2 \leq 0
  • Since x20x_2 \geq 0 (non-negativity), we have x2=0x_2 = 0
  • Solution: x1=4,x2=0x_1 = 4, x_2 = 0 (integer solution)
  • Profit = 3(4)+2(0)=12+0=123(4) + 2(0) = 12 + 0 = 12

Branch 1.1: x13x_1 \leq 3 and x21x_2 \leq 1

  • With x1=3x_1 = 3 and x2=1x_2 = 1:
  • Verification: Machine A: 2(3)+3(1)=6+3=9122(3) + 3(1) = 6 + 3 = 9 \leq 12
  • Verification: Machine B: 4(3)+2(1)=12+2=14164(3) + 2(1) = 12 + 2 = 14 \leq 16
  • Solution: x1=3,x2=1x_1 = 3, x_2 = 1 (integer solution)
  • Profit = 3(3)+2(1)=9+2=113(3) + 2(1) = 9 + 2 = 11

Branch 1.2: x13x_1 \leq 3 and x22x_2 \geq 2

  • With x1=3x_1 = 3 and x2=2x_2 = 2:
  • Verification: Machine A: 2(3)+3(2)=6+6=12122(3) + 3(2) = 6 + 6 = 12 \leq 12
  • Verification: Machine B: 4(3)+2(2)=12+4=16164(3) + 2(2) = 12 + 4 = 16 \leq 16
  • Solution: x1=3,x2=2x_1 = 3, x_2 = 2 (integer solution)
  • Profit = 3(3)+2(2)=9+4=133(3) + 2(2) = 9 + 4 = 13

Step 5: Compare Integer Solutions

We have found three feasible integer solutions:

  • Solution 1: x1=3,x2=1x_1 = 3, x_2 = 1, Profit = 3(3)+2(1)=9+2=113(3) + 2(1) = 9 + 2 = 11
  • Solution 2: x1=4,x2=0x_1 = 4, x_2 = 0, Profit = 3(4)+2(0)=12+0=123(4) + 2(0) = 12 + 0 = 12
  • Solution 3: x1=3,x2=2x_1 = 3, x_2 = 2, Profit = 3(3)+2(2)=9+4=133(3) + 2(2) = 9 + 4 = 13

Since we're maximizing profit, the optimal integer solution is Solution 3: x1=3,x2=2x_1 = 3, x_2 = 2 with a profit of \13$.

Note: In this example, the LP relaxation solution (3,2)(3, 2) happens to be an integer solution, so the integer constraint doesn't reduce the optimal value. This is not always the case. Often, integer constraints force us to accept a lower objective value than the LP relaxation.

Let's visualize this example to see the LP relaxation solution compared to the integer solutions:

Out[5]:
Visualization
Notebook output

LP relaxation solution showing the continuous feasible region and optimal point. The green marker indicates the optimal solution at (3, 2) with profit of $13. In this case, the LP relaxation solution happens to be an integer solution, demonstrating that integer constraints don't always reduce the optimal value.

Notebook output

Integer solutions within the feasible region with their associated profits. Each point represents a feasible integer combination of Product 1 and Product 2. The optimal integer solution at (3, 2) with profit $13 is highlighted in green. Points with profit ≥ $10 are shown in magenta, while lower-profit points are shown in gray.

Out[6]:
LP Relaxation Solution: x₁ = 3, x₂ = 2, Profit = \$13
Optimal Integer Solution: x₁ = 3, x₂ = 2, Profit = \$13

Note: In this case, the LP relaxation happens to give an integer solution. However, the integer constraint ensures we only consider feasible integer points

Implementation in Google OR-Tools

Google OR-Tools provides an excellent framework for solving MILP problems with its CP-SAT solver, which is specifically designed for constraint programming and mixed integer problems. OR-Tools offers both a Python API and C++ implementation, making it accessible for data scientists while maintaining high performance.

Basic Factory Optimization

Let's start with a simple factory optimization problem that demonstrates the core MILP concepts. We'll solve the two-product problem from our earlier example using OR-Tools.

First, we set up the problem by creating a solver, defining variables, and specifying constraints:

In[7]:
1from ortools.linear_solver import pywraplp
2import numpy as np
3
4def solve_factory_optimization():
5    """
6    Solve a factory optimization problem using Google OR-Tools MILP solver.
7    
8    Problem: Maximize profit from two products with machine capacity constraints.
9    """
10    # Create the solver
11    solver = pywraplp.Solver.CreateSolver('SCIP')
12    if not solver:
13        return None
14    
15    # Define variables
16    # x1 = units of product 1 to produce
17    # x2 = units of product 2 to produce
18    x1 = solver.IntVar(0, solver.infinity(), 'x1')
19    x2 = solver.IntVar(0, solver.infinity(), 'x2')
20    
21    # Define constraints
22    # Machine A: 2x1 + 3x2 <= 12
23    constraint_a = solver.Constraint(-solver.infinity(), 12)
24    constraint_a.SetCoefficient(x1, 2)
25    constraint_a.SetCoefficient(x2, 3)
26    
27    # Machine B: 4x1 + 2x2 <= 16
28    constraint_b = solver.Constraint(-solver.infinity(), 16)
29    constraint_b.SetCoefficient(x1, 4)
30    constraint_b.SetCoefficient(x2, 2)
31    
32    # Objective: maximize 3x1 + 2x2
33    objective = solver.Objective()
34    objective.SetCoefficient(x1, 3)
35    objective.SetCoefficient(x2, 2)
36    objective.SetMaximization()
37    
38    # Solve the problem
39    status = solver.Solve()
40    
41    if status == pywraplp.Solver.OPTIMAL:
42        # Calculate solution values
43        x1_value = x1.solution_value()
44        x2_value = x2.solution_value()
45        profit = solver.Objective().Value()
46        
47        # Verify constraints
48        machine_a_usage = 2*x1_value + 3*x2_value
49        machine_b_usage = 4*x1_value + 2*x2_value
50        
51        return {
52            'x1': x1_value,
53            'x2': x2_value,
54            'profit': profit,
55            'machine_a_usage': machine_a_usage,
56            'machine_b_usage': machine_b_usage,
57            'status': 'OPTIMAL'
58        }
59    else:
60        return {'status': 'NO_SOLUTION'}
61
62# Solve the problem
63result = solve_factory_optimization()

Now let's display the results:

Out[8]:
Optimal solution found!
Product 1 (x₁): 3 units
Product 2 (x₂): 2 units
Total profit: $13.00

Constraint verification:
Machine A usage: 12.0 hours (capacity: 12)
Machine B usage: 16.0 hours (capacity: 16)

The solver found an optimal solution. The production quantities and profit values shown above match our earlier manual calculation. The constraint verification confirms that both machines are fully utilized, with Machine A using exactly its full capacity of 12 hours and Machine B using its full capacity of 16 hours. This full utilization indicates that the solution is efficient and that the capacity constraints are binding, meaning we cannot increase production without violating constraints. When both constraints are binding at the optimal solution, it suggests that the problem is well-formulated and that the solver has found the true optimal point where no further improvement is possible.

Advanced Factory Optimization with Setup Costs

Now let's tackle a more realistic factory optimization problem that includes setup costs and multiple products. This demonstrates how binary variables model discrete setup decisions alongside continuous production quantities.

We'll optimize production of three products across three machines, where each product requires a setup (incurring a fixed cost) before production can begin:

In[9]:
1def solve_advanced_factory_optimization():
2    """
3    Advanced factory optimization with setup costs and multiple products.
4    
5    Problem: Optimize production of 3 products with:
6    - Setup costs (binary decisions)
7    - Machine capacity constraints
8    - Demand constraints
9    - Profit maximization
10    """
11    # Create the solver
12    solver = pywraplp.Solver.CreateSolver('SCIP')
13    if not solver:
14        return None
15    
16    # Problem parameters
17    products = ['Product_A', 'Product_B', 'Product_C']
18    machines = ['Machine_1', 'Machine_2', 'Machine_3']
19    
20    # Production data: [product][machine] = hours required
21    # Product A: efficient on Machine 2, uses Machine 1 and 3 moderately
22    # Product B: heavy on Machine 2 (bottleneck), efficient elsewhere
23    # Product C: balanced but uses all machines equally
24    production_time = [
25        [3, 1, 2],  # Product A: efficient, especially on Machine 2
26        [1, 4, 2],  # Product B: heavy on Machine 2 bottleneck
27        [3, 3, 3]   # Product C: balanced but uses more total time
28    ]
29    
30    # Machine capacities (hours) - Machine 2 is the bottleneck
31    machine_capacity = [28, 18, 24]  # Machine 2 is tightest constraint
32    
33    # Profit per unit - make Product A competitive
34    profit_per_unit = [14, 11, 15]  # Product A and C are close
35    
36    # Setup costs - create interesting trade-offs
37    # Product A: moderate setup, good profit, efficient production
38    # Product B: low setup, moderate profit, but bottleneck constraint
39    # Product C: higher setup, best profit, but uses more machine time
40    setup_cost = [30, 18, 50]  # Product C setup is higher
41    
42    # Demand constraints - allow reasonable production
43    max_demand = [7, 5, 8]
44    
45    # Create variables
46    # x[i] = units of product i to produce
47    x = []
48    for i in range(len(products)):
49        x.append(solver.IntVar(0, max_demand[i], f'x_{i}'))
50    
51    # y[i] = 1 if product i is produced (setup), 0 otherwise
52    y = []
53    for i in range(len(products)):
54        y.append(solver.IntVar(0, 1, f'y_{i}'))
55    
56    # Machine capacity constraints
57    for j in range(len(machines)):
58        constraint = solver.Constraint(-solver.infinity(), machine_capacity[j])
59        for i in range(len(products)):
60            constraint.SetCoefficient(x[i], production_time[i][j])
61        constraint.SetCoefficient(y[j], 0)  # Setup doesn't consume machine time
62    
63    # Setup constraints: if x[i] > 0, then y[i] = 1
64    # This is enforced by: x[i] <= max_demand[i] * y[i]
65    for i in range(len(products)):
66        constraint = solver.Constraint(-solver.infinity(), 0)
67        constraint.SetCoefficient(x[i], 1)
68        constraint.SetCoefficient(y[i], -max_demand[i])
69    
70    # Objective: maximize profit - setup costs
71    objective = solver.Objective()
72    for i in range(len(products)):
73        objective.SetCoefficient(x[i], profit_per_unit[i])
74        objective.SetCoefficient(y[i], -setup_cost[i])
75    objective.SetMaximization()
76    
77    # Solve
78    status = solver.Solve()
79    
80    if status == pywraplp.Solver.OPTIMAL:
81        # Extract solution values
82        production = [x[i].solution_value() for i in range(len(products))]
83        setup = [y[i].solution_value() for i in range(len(products))]
84        
85        # Calculate detailed metrics
86        total_profit = 0
87        total_setup_cost = 0
88        product_details = []
89        
90        for i, product in enumerate(products):
91            units = production[i]
92            setup_flag = setup[i]
93            product_profit = units * profit_per_unit[i]
94            product_setup = setup_flag * setup_cost[i]
95            net_contribution = product_profit - product_setup
96            
97            product_details.append({
98                'product': product,
99                'units': units,
100                'setup': setup_flag,
101                'product_profit': product_profit,
102                'setup_cost': product_setup,
103                'net_contribution': net_contribution
104            })
105            
106            total_profit += product_profit
107            total_setup_cost += product_setup
108        
109        # Calculate machine utilization
110        machine_utilization = []
111        for j, machine in enumerate(machines):
112            usage = sum(production_time[i][j] * production[i] 
113                       for i in range(len(products)))
114            utilization = usage / machine_capacity[j] * 100
115            machine_utilization.append({
116                'machine': machine,
117                'usage': usage,
118                'capacity': machine_capacity[j],
119                'utilization': utilization
120            })
121        
122        return {
123            'production': production,
124            'setup': setup,
125            'total_profit': total_profit,
126            'total_setup_cost': total_setup_cost,
127            'net_profit': total_profit - total_setup_cost,
128            'product_details': product_details,
129            'machine_utilization': machine_utilization,
130            'status': 'OPTIMAL'
131        }
132    else:
133        return {'status': 'NO_SOLUTION'}
134
135# Solve the advanced problem
136advanced_result = solve_advanced_factory_optimization()

Let's examine the optimization results:

Out[10]:
Advanced Factory Optimization Results:
==================================================
Product_A:
  Units produced: 7
  Setup required: Yes
  Product profit: $98.00
  Setup cost: $30.00
  Net contribution: $68.00

Product_B:
  Units produced: 2
  Setup required: Yes
  Product profit: $22.00
  Setup cost: $18.00
  Net contribution: $4.00

Product_C:
  Units produced: 0
  Setup required: No
  Product profit: $0.00
  Setup cost: $0.00
  Net contribution: $0.00

Total profit from production: $120.00
Total setup costs: $48.00
Net profit: $72.00

Machine Utilization:
Machine_1: 23.0/28 hours (82.1%)
Machine_2: 15.0/18 hours (83.3%)
Machine_3: 18.0/24 hours (75.0%)

The optimization results demonstrate how the solver balances production quantities against setup costs. Products with higher profit margins and lower setup costs are prioritized in the optimal solution, while products that don't contribute enough to cover their setup costs may be excluded entirely. For example, if a product has a setup cost of $50 but only generates $40 in profit, the solver will set its production to zero rather than incurring a net loss.

The machine utilization percentages indicate how efficiently each machine is being used. Values near 100% suggest that machine capacity is a binding constraint, meaning that increasing capacity would allow for higher production and potentially greater profit. Lower utilization (e.g., below 80%) might indicate that other constraints, such as demand limits, setup cost trade-offs, or resource availability on other machines, are preventing full machine usage. In such cases, the bottleneck is not the machine capacity itself but rather other factors in the optimization problem.

Let's visualize the results from the advanced factory optimization:

Out[11]:
Visualization
Notebook output

Production quantities for each product showing units produced. Green bars indicate products that are being produced, while red bars (if any) would indicate zero production. This visualization helps identify which products are included in the optimal solution.

Notebook output

Net contribution per product calculated as production profit minus setup costs. Positive values (blue) indicate profitable products that contribute to overall net profit, while negative values (red) would indicate products that don't cover their setup costs. This metric helps understand the economic contribution of each product.

Notebook output

Machine utilization percentages showing how efficiently each machine is being used. Values near 100% indicate binding capacity constraints, while lower values suggest available capacity. Orange bars indicate machines below capacity, while red bars indicate machines at full capacity.

Notebook output

Setup decisions showing which products require setup in the optimal solution. The pie chart displays the proportion of products that have setup costs incurred, helping visualize the discrete decision-making aspect of the MILP problem.

Out[12]:
Total Net Profit: $72.00
  - Production Profit: $120.00
  - Setup Costs: $48.00

Products Produced: 2 out of 3
Average Machine Utilization: 80.2%

Sensitivity Analysis

Understanding how changes in problem parameters affect optimal solutions is crucial for decision-making. Let's perform sensitivity analysis by solving the optimization problem under different scenarios:

In[13]:
1def solve_scenario(profit, setup):
2    """Solve a single scenario and return results"""
3    # Try multiple solvers in order of preference
4    solver_names = ["SCIP", "CBC", "GLOP"]
5    solver = None
6    for solver_name in solver_names:
7        solver = pywraplp.Solver.CreateSolver(solver_name)
8        if solver:
9            break
10
11    if not solver:
12        return None
13
14    x = [solver.IntVar(0, 10, f"x_{i}") for i in range(3)]
15    y = [solver.IntVar(0, 1, f"y_{i}") for i in range(3)]
16
17    production_time = [[2, 1, 3], [1, 3, 2], [3, 2, 1]]
18    machine_capacity = [20, 25, 15]
19
20    # Machine capacity constraints
21    for j in range(3):
22        constraint = solver.Constraint(-solver.infinity(), machine_capacity[j])
23        for i in range(3):
24            constraint.SetCoefficient(x[i], production_time[i][j])
25
26    # Setup constraints: if x[i] > 0, then y[i] = 1
27    for i in range(3):
28        constraint = solver.Constraint(-solver.infinity(), 0)
29        constraint.SetCoefficient(x[i], 1)
30        constraint.SetCoefficient(y[i], -10)
31
32    # Objective: maximize profit - setup costs
33    objective = solver.Objective()
34    for i in range(3):
35        objective.SetCoefficient(x[i], profit[i])
36        objective.SetCoefficient(y[i], -setup[i])
37    objective.SetMaximization()
38
39    # Solve with a time limit
40    solver.SetTimeLimit(5000)
41    status = solver.Solve()
42
43    if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
44        return {
45            "net_profit": solver.Objective().Value(),
46            "production": [x[i].solution_value() for i in range(3)],
47            "setup": [y[i].solution_value() for i in range(3)],
48        }
49    else:
50        return {
51            "net_profit": 0.0,
52            "production": [0.0, 0.0, 0.0],
53            "setup": [0.0, 0.0, 0.0],
54        }
55
56
57# Define scenarios with adjusted parameters to ensure profitable solutions
58scenarios = [
59    ("Original", [15, 12, 18], [50, 40, 60]),
60    ("Higher\nProduct\nProfit", [20, 18, 25], [50, 40, 60]),
61    ("Lower\nSetup\nCosts", [15, 12, 18], [25, 20, 30]),
62    ("High\nSetup\nCosts", [15, 12, 18], [100, 80, 120]),
63]
64
65# Solve all scenarios
66sensitivity_results = []
67for name, profit, setup in scenarios:
68    result = solve_scenario(profit, setup)
69    if result:
70        sensitivity_results.append((name, result))
71    else:
72        sensitivity_results.append(
73            (
74                name,
75                {
76                    "net_profit": 0.0,
77                    "production": [0.0, 0.0, 0.0],
78                    "setup": [0.0, 0.0, 0.0],
79                },
80            )
81        )

Now let's display the sensitivity analysis results:

Out[14]:
Sensitivity Analysis: Impact of Parameter Changes
============================================================

Original:
------------------------------
Net Profit: $50.00
Production: ['0', '5', '5']
Setup Decisions: ['0', '1', '1']

Higher Product Profit:
------------------------------
Net Profit: $115.00
Production: ['0', '5', '5']
Setup Decisions: ['0', '1', '1']

Lower Setup Costs:
------------------------------
Net Profit: $100.00
Production: ['0', '5', '5']
Setup Decisions: ['0', '1', '1']

High Setup Costs:
------------------------------
Net Profit: $4.00
Production: ['0', '7', '0']
Setup Decisions: ['0', '1', '0']

The sensitivity analysis reveals how parameter changes affect optimal decisions. Comparing the scenarios shown above, higher product profits generally lead to increased production and higher net profits, as the additional revenue more than compensates for setup costs. When product profits increase by 25-40% (as in the "Higher Product Profit" scenario), the solver can justify additional setups and higher production volumes.

Lower setup costs make it economically viable to produce more products, potentially increasing total production volume. When setup costs are reduced by 50% (as in the "Lower Setup Costs" scenario), products that were previously unprofitable due to high setup costs may now be included in the optimal solution. Conversely, high setup costs (doubled in the "High Setup Costs" scenario) may cause the optimizer to reduce the number of products produced, focusing only on those with the best profit-to-setup-cost ratios. This analysis helps factory managers understand which parameters have the most impact on profitability and where to focus improvement efforts, such as negotiating lower setup costs or improving production efficiency to increase profit margins.

Let's visualize the sensitivity analysis results to compare how different scenarios affect the optimal solution:

Out[15]:
Visualization
Notebook output

Net profit comparison across different parameter scenarios. The bar chart shows how changes in profit margins and setup costs affect overall profitability. Higher product profits and lower setup costs generally lead to increased net profit, while high setup costs can significantly reduce profitability.

Notebook output

Production quantities by product across different scenarios. The grouped bar chart compares how each scenario affects production decisions for each product. This visualization reveals how parameter changes influence which products are produced and in what quantities, demonstrating the sensitivity of optimal solutions to input parameters.

Out[16]:

Original:
  Net Profit: $50.00
  Production: ['0', '5', '5']
  Setup Decisions: ['0', '1', '1']

Higher Product Profit:
  Net Profit: $115.00
  Production: ['0', '5', '5']
  Setup Decisions: ['0', '1', '1']

Lower Setup Costs:
  Net Profit: $100.00
  Production: ['0', '5', '5']
  Setup Decisions: ['0', '1', '1']

High Setup Costs:
  Net Profit: $4.00
  Production: ['0', '7', '0']
  Setup Decisions: ['0', '1', '0']

Key Parameters

Below are some of the main parameters and methods that affect how MILP solvers work and perform in Google OR-Tools:

Key Solver Parameters

  • Solver.CreateSolver(solver_name): Creates a solver instance. Common options include 'SCIP' (recommended for MILP), 'CBC' (open-source alternative), and 'GLOP' (for linear programming only). SCIP typically provides the best performance for mixed integer problems but requires a license for commercial use.

  • SetTimeLimit(seconds): Sets the maximum time in milliseconds for the solver to run (default: no limit). Use this to prevent the solver from running indefinitely on difficult problems. Typical values range from 1000-10000 milliseconds depending on problem complexity.

  • SetNumThreads(n): Sets the number of threads for parallel solving (default: uses all available cores). For large problems, increasing threads can speed up solution time, but may not always improve performance due to overhead.

Key Variable Creation Methods

  • IntVar(lower_bound, upper_bound, name): Creates an integer variable with specified bounds. Use solver.infinity() for unbounded variables. Integer variables are important for modeling discrete decisions like production quantities that should be whole numbers.

  • BoolVar(name): Creates a binary variable (equivalent to IntVar(0, 1, name)). Binary variables model yes/no decisions like setup choices or machine selection. More efficient than integer variables for binary decisions.

  • NumVar(lower_bound, upper_bound, name): Creates a continuous variable for quantities that can take fractional values. Use when integer constraints are not required, as continuous variables solve faster.

Key Constraint Methods

  • Constraint(lower_bound, upper_bound): Creates a linear constraint with specified bounds. Use -solver.infinity() for unbounded lower bounds and solver.infinity() for unbounded upper bounds. The constraint is enforced as: lower_bound ≤ expression ≤ upper_bound.

  • SetCoefficient(variable, coefficient): Sets the coefficient for a variable in a constraint. Call this method for each variable in the constraint expression. Coefficients can be positive or negative to model resource consumption or production.

Key Objective Methods

  • SetMaximization(): Sets the objective to maximize (default is minimization). Use for profit maximization problems. The objective function is built by setting coefficients for each variable.

  • SetMinimization(): Sets the objective to minimize. Use for cost minimization problems. This is the default behavior if neither method is called.

  • SetCoefficient(variable, coefficient): Sets the coefficient for a variable in the objective function. Positive coefficients for maximization problems, negative for minimization (or vice versa depending on whether you're maximizing profit or minimizing cost).

Key Solution Methods

  • Solve(): Solves the optimization problem and returns a status code. Returns Solver.OPTIMAL if an optimal solution was found, Solver.FEASIBLE if a feasible but not necessarily optimal solution was found, or other status codes for infeasible or unbounded problems.

  • solution_value(): Returns the value of a variable in the optimal solution. Call this method on each variable after a successful solve to extract the solution values.

  • Objective().Value(): Returns the value of the objective function at the optimal solution. Use this to get the total profit or cost after solving.

Practical Applications

Practical Implications

MILP is particularly valuable in production planning and scheduling scenarios where factories need to make discrete decisions about which products to produce while optimizing continuous production quantities. This dual nature makes MILP well-suited for make-to-order manufacturing environments where demand varies significantly and setup costs are substantial. The ability to model both binary setup decisions and continuous production quantities in a single optimization framework allows factory managers to balance the trade-off between setup costs and production efficiency. For example, in job shop manufacturing where each order requires machine setup, MILP can determine which orders to accept and in what sequence to maximize profit while respecting capacity constraints.

The method is also effective for capacity planning and resource allocation problems where factories need to decide whether to invest in additional machines or production lines. MILP can model these strategic decisions alongside tactical production scheduling, enabling integrated optimization of both short-term operations and long-term investments. This integrated approach is particularly valuable in industries with high capital costs and complex production interdependencies, such as automotive manufacturing, semiconductor production, and chemical processing. In these contexts, the ability to simultaneously optimize production schedules and capacity investments can lead to significant cost savings compared to sequential decision-making approaches.

MILP becomes less appropriate when problems scale beyond several hundred integer variables, as computational time grows exponentially with the number of discrete decisions. For very large-scale problems with thousands of products and machines, decomposition methods or heuristic approaches may be more practical. Additionally, if the relationships between variables are highly nonlinear, MILP's linearity assumption may require significant approximation that compromises solution quality. In such cases, nonlinear programming or specialized algorithms designed for the specific problem structure may provide better results.

Best Practices

To achieve effective MILP solutions, start by carefully modeling the problem structure. Use binary variables for setup decisions, machine selection, and other yes/no choices, while reserving continuous variables for production quantities and resource allocations. When modeling setup costs, ensure that binary variables are properly linked to production variables using big-M constraints with appropriately sized M values. Calculate M based on problem-specific bounds rather than using arbitrarily large numbers. For example, if maximum production capacity for a product is 100 units, use M=100 rather than M=10000. This prevents numerical instability and improves solver performance by creating tighter linear relaxations.

For solver selection, commercial solvers like Gurobi or CPLEX often provide superior performance and reliability for production systems, though they require licensing fees. When using open-source solvers like SCIP or CBC, configure appropriate parameters to balance solution quality and computational time. Set time limits based on problem size: 300-600 seconds for problems with fewer than 100 integer variables, 600-1800 seconds for problems with 100-500 variables, and consider longer limits or decomposition strategies for larger problems. Set optimality gap tolerances between 0.01 and 0.05 (1-5%) depending on your solution quality requirements. For problems where near-optimal solutions are acceptable, larger gaps (0.05-0.10) can significantly reduce solve times. Validate solutions by checking constraint satisfaction and comparing against known feasible solutions or historical performance data.

When formulating constraints, avoid redundant constraints that don't tighten the feasible region, as they can slow down the solver without providing benefit. Use constraint aggregation where possible to reduce the number of constraints, but be careful not to lose important problem structure that could help the solver prune the search space. For multi-period problems, consider time-indexed formulations that explicitly model temporal relationships, as these often provide tighter linear relaxations than alternative formulations. When modeling capacity constraints, include both aggregate and detailed constraints when they provide complementary information, but remove constraints that are dominated by others.

Data Requirements and Preprocessing

MILP models require accurate data on production times, machine capacities, setup costs, and demand forecasts. Production time estimates should be standardized across all products and machines, using consistent units (typically hours) and accounting for variability through buffer times or stochastic modeling. Machine capacities should reflect actual available time rather than theoretical maximums, accounting for maintenance windows, shift schedules, and other operational constraints. For example, if a machine operates 8 hours per shift with 30 minutes of scheduled maintenance, use 7.5 hours as the capacity rather than the full 8 hours. Setup costs should include both direct costs (labor, materials) and opportunity costs (lost production time), as omitting opportunity costs can lead to suboptimal solutions that minimize direct expenses but reduce overall throughput.

Demand forecasts require careful preprocessing to ensure they represent realistic production requirements. Aggregate demand data to appropriate time buckets that match the optimization horizon, and validate forecasts against historical patterns to identify potential anomalies. For multi-product scenarios, ensure demand data includes product mix requirements and any dependencies between products. When demand uncertainty is significant, consider robust optimization approaches or scenario-based formulations rather than relying solely on point forecasts. For time-indexed problems, ensure demand data is aligned with the time period structure of the model, and handle any missing or incomplete data through appropriate imputation methods or constraint adjustments.

Data quality directly impacts solution quality, so invest in robust data collection systems that capture production times, machine utilization, and setup activities accurately. Implement data validation checks to identify outliers or inconsistencies before optimization runs. For production time data, flag values that deviate more than 20% from historical averages for manual review. Regular calibration of model parameters against actual performance data helps maintain accuracy as production conditions evolve. Compare model predictions to actual outcomes monthly or quarterly, and adjust parameters when systematic discrepancies exceed 5-10%. Standardize data collection procedures across different production lines or facilities to ensure consistency when scaling models to multiple locations.

Common Pitfalls

One frequent mistake is using overly large big-M values when linking binary and continuous variables, which creates weak linear relaxations and slows solver performance. Instead, calculate M values based on problem-specific bounds. For example, if maximum production is 100 units, use M=100 rather than M=10000. Another common issue is failing to properly model setup constraints, leading to solutions where production occurs without corresponding setup activities. Include explicit constraints that force binary setup variables to equal 1 when production quantities are positive, using constraints of the form xiMyix_i \leq M \cdot y_i where xix_i is production and yiy_i is the setup binary variable. Additionally, ensure that setup costs are only incurred when the binary variable equals 1, not when production is positive.

Neglecting to validate that solutions are actually implementable in the factory can lead to theoretically optimal but practically infeasible plans. Solutions may violate constraints that weren't explicitly modeled, such as material handling limitations, worker availability, or quality control requirements. Review solutions with production managers and validate against physical constraints before implementation. Additionally, beware of over-optimizing on cost metrics while ignoring other important objectives like delivery reliability or quality, which may require multi-objective formulations or constraint-based approaches. A solution that minimizes cost but consistently misses delivery deadlines may be worse than a slightly more expensive but reliable alternative.

Another pitfall is assuming that the optimal solution will remain optimal as conditions change. MILP solutions are sensitive to parameter values, and small changes in costs, capacities, or demands can significantly alter optimal decisions. Perform sensitivity analysis to understand solution robustness and identify key parameters that require careful monitoring. For example, if a 5% change in setup costs causes a 20% change in the optimal solution, those setup costs are key parameters that need accurate estimation. Finally, avoid the temptation to model every detail of the production system, as overly complex models become difficult to solve and maintain. Focus on the most important constraints and decisions that drive optimization value, typically the top 20% of constraints that have the most impact on the objective function.

Computational Considerations

MILP computational complexity grows exponentially with the number of integer variables, making problem size a key consideration. For problems with fewer than 100 integer variables, most modern solvers can find optimal solutions within minutes on standard hardware. Problems with 100-500 integer variables may require several minutes to hours depending on problem structure, while problems with more than 500 integer variables often require decomposition strategies or heuristic methods. The number of continuous variables has less impact on computational time, as linear programming relaxations solve efficiently even with thousands of continuous variables. As a rule of thumb, if your problem has more than 1000 integer variables or requires more than 1 hour to solve, consider decomposition or approximation methods.

Problem structure significantly affects solver performance. Problems with tight linear relaxations (where the LP relaxation provides a good bound) solve much faster than problems with weak relaxations. To improve performance, focus on formulating constraints that create tight bounds, such as using aggregated capacity constraints alongside detailed resource constraints. Symmetry in the problem formulation can also slow down branch-and-bound algorithms, so break symmetry by adding constraints that eliminate equivalent solutions or by using specialized branching strategies. For example, if products are identical except for labels, add constraints that order them by index to eliminate symmetric solutions.

For large-scale problems, consider decomposition approaches such as Benders decomposition or Lagrangian relaxation that separate the problem into more manageable subproblems. Time-indexed formulations can be decomposed by time period, while multi-product problems can sometimes be decomposed by product family. When exact solutions are computationally infeasible, use heuristic methods like local search or genetic algorithms, or employ hybrid approaches that use MILP for strategic decisions and heuristics for detailed scheduling. Set appropriate solver parameters including time limits (300-1800 seconds for medium problems), memory limits (4-16 GB depending on problem size), and optimality gap tolerances (0.01-0.05) based on your computational resources and solution quality requirements. For problems that consistently hit time limits, consider increasing the optimality gap tolerance to 0.10 to obtain feasible solutions more quickly.

Performance and Deployment Considerations

Evaluate MILP solutions using both optimization metrics and practical performance indicators. Optimization metrics include the objective function value, optimality gap (difference between best solution and best bound, typically expressed as a percentage), and constraint satisfaction. However, these metrics alone don't guarantee practical success. Compare optimized solutions against baseline performance using key operational metrics such as on-time delivery rates, machine utilization, inventory levels, and overall equipment effectiveness (OEE). A solution with a 10% improvement in the objective function but a 5% decrease in on-time delivery may not be acceptable. Validate that theoretical improvements translate to actual performance gains through pilot implementations before full-scale deployment, typically running parallel systems for 2-4 weeks to compare outcomes.

Deploy MILP models in production environments by integrating with existing enterprise systems including ERP, MES, and supply chain management platforms. This integration ensures optimization runs use real-time data and that optimized schedules can be automatically executed in production systems. Implement automated data pipelines that extract required parameters from source systems, run optimization models on a regular schedule (typically daily or weekly depending on planning horizon), and push results back to execution systems. Include validation checks that flag solutions with unusual characteristics (e.g., production quantities more than 20% different from historical averages) or constraint violations before deployment. Set up automated alerts for cases where the solver fails to find a feasible solution or exceeds time limits.

Monitor model performance continuously to detect when updates are needed. Track the gap between predicted and actual production times, machine utilization, and setup durations to identify when model parameters require recalibration. If actual production times consistently differ from model predictions by more than 10%, recalibrate the model parameters. Establish triggers for model updates based on significant changes in production capabilities (new machines, capacity changes), introduction of new products, machine modifications, or shifts in market conditions (demand patterns, cost structures). Maintain version control for models and document changes to enable rollback if new versions perform worse than previous ones. Regular performance reviews comparing optimized plans against actual outcomes (monthly or quarterly) help identify improvement opportunities and validate that the optimization system continues to deliver value.

Summary

Mixed Integer Linear Programming provides a powerful framework for factory optimization that balances mathematical rigor with practical applicability. By combining continuous optimization for production quantities with discrete decision-making for setup and capacity choices, MILP addresses the fundamental challenges of manufacturing optimization in a unified framework.

The key strength of MILP lies in its ability to handle both tactical production decisions and strategic capacity planning within a single optimization model. This integrated approach enables factory managers to optimize not just individual production runs but also long-term resource allocation and investment decisions. The mathematical guarantees of optimality (within computational limits) provide confidence in decision-making for high-stakes manufacturing scenarios.

However, successful MILP implementation requires careful attention to data quality, model validation, and computational scalability. The exponential complexity of large-scale MILP problems means that practical applications often require problem decomposition, heuristic methods, or hybrid approaches that combine exact optimization with approximation techniques. Despite these challenges, MILP remains an important tool for factory optimization, providing the mathematical foundation for evidence-based manufacturing decisions that can significantly improve operational efficiency and profitability.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about Mixed Integer Linear Programming for factory optimization.

Loading component...

Reference

BIBTEXAcademic
@misc{mixedintegerlinearprogrammingmilpforfactoryoptimizationcompleteguidewithmathematicalfoundationsimplementation, author = {Michael Brenndoerfer}, title = {Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation}, year = {2025}, url = {https://mbrenndoerfer.com/writing/milp-factory-optimization-mixed-integer-linear-programming-production-planning}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-11-24} }
APAAcademic
Michael Brenndoerfer (2025). Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation. Retrieved from https://mbrenndoerfer.com/writing/milp-factory-optimization-mixed-integer-linear-programming-production-planning
MLAAcademic
Michael Brenndoerfer. "Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation." 2025. Web. 11/24/2025. <https://mbrenndoerfer.com/writing/milp-factory-optimization-mixed-integer-linear-programming-production-planning>.
CHICAGOAcademic
Michael Brenndoerfer. "Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation." Accessed 11/24/2025. https://mbrenndoerfer.com/writing/milp-factory-optimization-mixed-integer-linear-programming-production-planning.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation'. Available at: https://mbrenndoerfer.com/writing/milp-factory-optimization-mixed-integer-linear-programming-production-planning (Accessed: 11/24/2025).
SimpleBasic
Michael Brenndoerfer (2025). Mixed Integer Linear Programming (MILP) for Factory Optimization: Complete Guide with Mathematical Foundations & Implementation. https://mbrenndoerfer.com/writing/milp-factory-optimization-mixed-integer-linear-programming-production-planning
Michael Brenndoerfer

About the author: Michael Brenndoerfer

All opinions expressed here are my own and do not reflect the views of my employer.

Michael currently works as an Associate Director of Data Science at EQT Partners in Singapore, where he drives AI and data initiatives across private capital investments.

With over a decade of experience spanning private equity, management consulting, and software engineering, he specializes in building and scaling analytics capabilities from the ground up. He has published research in leading AI conferences and holds expertise in machine learning, natural language processing, and value creation through data.

Stay updated

Get notified when I publish new articles on data and AI, private equity, technology, and more.