291. Integer Programming

291.1. IP (Integer Programming)

291.2. Selection and Logical Relations on Binary Variables 𝑥𝑖{0,1}

291.2.1. Cardinality Constraints

Given a subset 𝑆{1,,𝑛}:

𝑖𝑆𝑥𝑖𝑘
Example

We want to select at least 2 of items 1, 2, 3

𝑥1+𝑥2+𝑥32
𝑖𝑆𝑥𝑖𝑘
Example

We want to select at most 2 of items 1, 2, 3

𝑥1+𝑥2+𝑥32
𝑖𝑆𝑥𝑖=𝑘
Example

We want to select exactly 2 of items 1, 2, 3

𝑥1+𝑥2+𝑥3=2

291.2.2. Logical OR

Given a subset 𝑆{1,,𝑛}:

𝑖𝑆𝑥𝑖1
Example

Select item 1 or item 2, or both

𝑥1+𝑥21

291.2.3. Conditional Selection: “If X, then Y”

(Implication Constraints)

𝑥𝑎𝑥𝑏
Example

If item 1 is selected, then item 2 must also be selected

𝑥1𝑥2
𝑥𝑎𝑥𝑏Valid?
00
01
10
11
𝑥𝑎+𝑥𝑏1
Example

If you choose project A, then you cannot choose project B (mutually exclusive).

𝑥𝑎𝑥𝑏Valid?
00
01
10
11
𝑥𝑏+𝑥𝑐𝑀(1𝑥𝑎)
Example

For binary variables 𝑀=2 works

If you choose project A, then you must not choose either project B or C.

𝑥𝑎𝑥𝑏𝑥𝑐Valid?
100
110
111
011
001

291.2.4. Conditional OR: “If not X, then select Y and Z”

𝑖𝑆𝑥𝑖𝑘(1𝑥𝑗)
Example

If you do not choose item 1, then you must choose both item 2 and item

If 𝑥1=0, then 𝑥2=𝑥3=1

𝑥2+𝑥32(1𝑥1)
𝑥1𝑥2𝑥3LHSRHSValid?
01122
01012
00002
10000
11010
11120

291.2.5. XOR / Exclusive Conditions

𝑖𝑆𝑥𝑖=1𝑥𝑎+𝑥𝑏=1
Example

You can select item 1 or item 2, but not both

𝑥1𝑥2SumValid?
011
101
112
000

291.3. Constraint Activation and Relaxation via Binary Indicators 𝑧𝑖{1,0}

291.3.1. Base Case: At Least One of Two Constraints Satisfied

You are given 2 constraints

𝑔1(𝑥)𝑏1𝑔2(𝑥)𝑏2

We want at least one of these two constraints to be satisfied.

Binary Indicator Variables

Define:

𝑧𝑖={1if𝑔𝑖(𝑥)𝑏𝑖is enforced0if𝑔𝑖(𝑥)𝑏𝑖may be violatedfor𝑖=1,2

Let 𝑀1 and 𝑀2 be upper bounds on how much each constraint’s LHS 𝑔𝑖(𝑥) can exceed 𝑏𝑖:

𝑀𝑖max𝑥(𝑔𝑖(𝑥)𝑏𝑖)

Big-M Reformulation

We relax each constraint as follows:

𝑔1(𝑥)𝑏1𝑀1(1𝑧1)𝑔2(𝑥)𝑏2𝑀2(1𝑧2)

When 𝑧𝑖=1, the original constraint is enforced

When 𝑧𝑖=0, the original constraint can be violated by up to 𝑀𝑖

At Least One Constraint Enforced

We require:

𝑧1+𝑧21

291.3.2. General Formulation for 𝑚 Constraints with At Least 𝑘 Satisfied

  1. Introduce a binary variable 𝑧𝑖{0,1} for each constraint 𝑔𝑖(𝑥)𝑏𝑖, where:
  1. Use “big-M” constraint to relax 𝑔𝑖(𝑥)𝑏𝑖 when 𝑧𝑖=0, like:
𝑔𝑖(𝑥)𝑏𝑖𝑀𝑖(1𝑧𝑖),𝑖=1,,𝑚

Where 𝑀𝑖max𝑥(𝑔𝑖(𝑥)𝑏𝑖)

  1. Impose a condition that at least 𝑘 of the 𝑚 constraints must be satisfied:
𝑖=1𝑚𝑧𝑖𝑘

291.4. Fixed Charge Constraints (Setup Costs)

Decision Variables

𝑦𝑖={1if factory𝑖is used (i.e.,𝑥𝑖>0)0if factory𝑖is not used (i.e,𝑥𝑖=0)

Objective Function

min𝑖=1𝑛𝐶𝑖𝑥𝑖Production Cost+𝑖=1𝑛𝑆𝑖𝑦𝑖Setup Cost

Capacity Constraints

𝑥𝑖𝐾𝑖𝑖=1,,𝑛

Demand Fullfilment

𝑖=1𝑛𝑥𝑖𝐷

Logical Linking of 𝑥𝑖 and 𝑦𝑖

To ensure 𝑦𝑖=1 if any production occurs at factory 𝑖:

𝑥𝑖𝐾𝑖𝑦𝑖𝑖=1,,𝑛

Binary & Non-negative Constraints

𝑦𝑖{0,1},𝑥𝑖0𝑖=1,,𝑛

General Form (Big-M Formulation)

𝑥𝑀𝑦

Where 𝑀 must be set to the upper bound of 𝑥

291.5. Facility Location

ProblemDefinition
Set CoveringSelect the minimum number of facilities such that every demand point is within a specified coverage distance of at least one facility
Maximum CoveringSelect a fixed number of facilities to maximize the number of demand points covered within a specified distance
Fixed Charge LocationDetermine which facilities to open and how to assign demand to them to minimize the total cost

291.5.1. Set Covering Problem

Question: How to allocate as few facilities as possible to cover all demand nodes?

Complete Formulation

min𝑗𝐽𝑤𝑗𝑥𝑗𝑠.𝑡.𝑗𝐽𝑎𝑖𝑗𝑥𝑗1𝑖𝐼𝑥𝑗{0,1}𝑗𝐽

291.5.2. Maximum Covering Problem

Question: How to allocate at most 𝑝 facilities to cover as many demand nodes as possible?

Complete Formulation

min𝑖𝐼𝑤𝑖𝑦𝑖𝑠.𝑡.𝑗𝐽𝑎𝑖𝑗𝑥𝑗𝑦𝑖𝑖𝐼𝑗𝐽𝑥𝑗𝑝𝑗𝐽𝑥𝑗{0,1}𝑗𝐽𝑦𝑖{0,1}𝑖𝐼

Fixed Charge Location Problems

Question: How to allocate some facilities to minimize the total shipping and construction costs?

Uncapacitated Facility Location Problem (UFL)

Complete Formulation

min𝑖𝐼𝑗𝐽𝑖𝑑𝑖𝑗𝑦𝑖𝑗Shipping Costs+𝑗𝐽𝑓𝑗𝑥𝑗Fixed Costs𝑠.𝑡.𝑦𝑖𝑗𝑥𝑗𝑖𝐼,𝑗𝐽𝑗𝐽𝑦𝑖𝑗=1𝑖𝐼𝑥𝑗{0,1}𝑗𝐽𝑦𝑖𝑗{0,1}𝑖𝐼

Capacitated Facility Location Problem (CFL)

If locations have a capacity, we may add constraint

𝑖𝐼𝑖𝑦𝑖𝑗𝐾𝑗𝑗𝐽

Where:

Example
min𝑗=15𝑓𝑗𝑥𝑗Fixed Costs+𝑖=15𝑗=15𝑐𝑖𝑗𝑦𝑖𝑗Shipping Costs𝑠.𝑡.𝑖=15𝑦𝑖𝑗𝐾𝑗𝑥𝑗𝑗=1,,5(Capacity Constraint)𝑗=15𝑦𝑖𝑗𝐷𝑖𝑖=1,,5(Demand Constraint)𝑥𝑗{0,1}𝑗=1,,5(Binary Constraint)𝑦𝑖𝑗0𝑖=1,,5,𝑗=1,,5(Positivity Constraint)

Parameters

  • 𝑓𝑗: weekly operating cost of distribution center 𝑗
  • 𝑐𝑖𝑗: shipping cost per book from distribution center 𝑗 to region 𝑖
  • 𝐾𝑗: capacity of distribution center 𝑗
  • 𝐷𝑖: book demand of region 𝑖

Decision variable

  • 𝑥𝑖𝑗: binary variable
𝑥𝑖𝑗={1if a distribution center is build at location𝑗0otherwise
  • 𝑦𝑖𝑗: number of books shipped from distribution center 𝑗 to region 𝑖

Supply

WANVNEPAFLDemand
(𝐷𝑖)
NorthWest𝑦11𝑦12𝑦13𝑦14𝑦158000
SouthWest𝑦21𝑦22𝑦23𝑦24𝑦2512000
MidWest𝑦31𝑦32𝑦33𝑦34𝑦359000
SouthEast𝑦41𝑦42𝑦43𝑦44𝑦4514000
NorthEast𝑦51𝑦52𝑦53𝑦54𝑦5517000

Fixed Costs (𝑓𝑗) and Capacity (𝐾𝑗)

𝑥1𝑥2𝑥3𝑥4𝑥5
Operation Cost (𝑓𝑖)4000030000250004000030000
Capacity (𝐾𝑖)2000020000150002500015000

Shipping Cost (𝑐𝑗)

WANVNEPAFL
NorthWest2.43.254.055.256.95
SouthWest3.52.33.256.055.85
MidWest4.83.42.854.34.8
SouthEast6.85.254.33.252.1
NorthEast5.7564.752.753.5

Binary Decision Variable

WA𝑥1
NV𝑥2
NE𝑥3
PA𝑥4
FL𝑥5

model.py

model.py
from pyomo.environ import *
from pyomo.dataportal import DataPortal

model = AbstractModel()

# Sets
model.I = Set(doc='Regions (demand points)')
model.J = Set(doc='Candidate distribution centers')

# Parameters
model.D = Param(model.I, within=NonNegativeReals, doc='Demand at region i')
model.K = Param(model.J, within=NonNegativeReals, doc='Capacity at distribution center j')
model.f = Param(model.J, within=NonNegativeReals, doc='Fixed cost to open center j')
model.c = Param(model.I, model.J, within=NonNegativeReals, doc='Shipping cost from j to i')

# Decision Variables
model.x = Var(model.J, within=Binary, doc='1 if center j is opened')
model.y = Var(model.I, model.J, within=NonNegativeReals, doc='Units shipped from j to i')

# Objective Function: Minimize total cost
def total_cost_rule(model):
    fixed = sum(model.f[j] * model.x[j] for j in model.J)
    shipping = sum(model.c[i, j] * model.y[i, j] for i in model.I for j in model.J)
    return fixed + shipping
model.TotalCost = Objective(rule=total_cost_rule, sense=minimize)

# Constraints

# Capacity Constraint: Total shipped from center ≤ capacity if opened
def capacity_rule(model, j):
    return sum(model.y[i, j] for i in model.I) <= model.K[j] * model.x[j]
model.CapacityConstraint = Constraint(model.J, rule=capacity_rule)

# Demand Constraint: Total received by region ≥ its demand
def demand_rule(model, i):
    return sum(model.y[i, j] for j in model.J) >= model.D[i]
model.DemandConstraint = Constraint(model.I, rule=demand_rule)

# Load data from .dat file
data = DataPortal()
data.load(filename='data.dat', model=model)

# Create an instance of the model
instance = model.create_instance(data)

# Create solver
solver = SolverFactory('glpk')
solver.options['tmlim'] = 60

# Solve with solver timeout (optional)
results = solver.solve(instance, tee=True)

# Display results
instance.display()
data.dat
set I := NorthWest SouthWest MidWest SouthEast NorthEast;
set J := WA NV NE PA FL;

param: D :=
NorthWest 8000
SouthWest 12000
MidWest 9000
SouthEast 14000
NorthEast 17000;

param: f :=
WA 40000
NV 30000
NE 25000
PA 40000
FL 30000;

param: K :=
WA 20000
NV 20000
NE 15000
PA 25000
FL 15000;

param c:
        WA     NV     NE     PA     FL :=
NorthWest 2.4   3.25   4.05   5.25   6.95
SouthWest 3.5   2.3    3.25   6.05   5.85
MidWest   4.8   3.4    2.85   4.3    4.8
SouthEast 6.8   5.25   4.3    3.25   2.1
NorthEast 5.75  6      4.75   2.75   3.5 ;

Output:

Variables:
x : 1 if center j is opened
    Size=5, Index=J
    Key : Lower : Value : Upper : Fixed : Stale : Domain
     FL :     0 :   1.0 :     1 : False : False : Binary
     NE :     0 :   0.0 :     1 : False : False : Binary
     NV :     0 :   1.0 :     1 : False : False : Binary
     PA :     0 :   1.0 :     1 : False : False : Binary
     WA :     0 :   0.0 :     1 : False : False : Binary
y : Units shipped from j to i
    Size=25, Index=I*J
    Key                 : Lower : Value   : Upper : Fixed : Stale : Domain
      ('MidWest', 'FL') :     0 :  1000.0 :  None : False : False : NonNegativeReals
      ('MidWest', 'NE') :     0 :     0.0 :  None : False : False : NonNegativeReals
      ('MidWest', 'NV') :     0 :     0.0 :  None : False : False : NonNegativeReals
      ('MidWest', 'PA') :     0 :  8000.0 :  None : False : False : NonNegativeReals
      ('MidWest', 'WA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthEast', 'FL') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthEast', 'NE') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthEast', 'NV') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthEast', 'PA') :     0 : 17000.0 :  None : False : False : NonNegativeReals
    ('NorthEast', 'WA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthWest', 'FL') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthWest', 'NE') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthWest', 'NV') :     0 :  8000.0 :  None : False : False : NonNegativeReals
    ('NorthWest', 'PA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('NorthWest', 'WA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthEast', 'FL') :     0 : 14000.0 :  None : False : False : NonNegativeReals
    ('SouthEast', 'NE') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthEast', 'NV') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthEast', 'PA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthEast', 'WA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthWest', 'FL') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthWest', 'NE') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthWest', 'NV') :     0 : 12000.0 :  None : False : False : NonNegativeReals
    ('SouthWest', 'PA') :     0 :     0.0 :  None : False : False : NonNegativeReals
    ('SouthWest', 'WA') :     0 :     0.0 :  None : False : False : NonNegativeReals

Objectives:
  TotalCost : Size=1, Index=None, Active=True
      Key  : Active : Value
      None :   True : 268950.0

Constraints:
  CapacityConstraint : Size=5
      Key : Lower : Body : Upper
      FL :  None :  0.0 :   0.0
      NE :  None :  0.0 :   0.0
      NV :  None :  0.0 :   0.0
      PA :  None :  0.0 :   0.0
      WA :  None :  0.0 :   0.0
  DemandConstraint : Size=5
      Key       : Lower   : Body    : Upper
        MidWest :  9000.0 :  9000.0 :  None
      NorthEast : 17000.0 : 17000.0 :  None
      NorthWest :  8000.0 :  8000.0 :  None
      SouthEast : 14000.0 : 14000.0 :  None
      SouthWest : 12000.0 : 12000.0 :  None

291.6. Machine Scheduling

ProblemDefinition
One
Machine
Single Machine
Serial Production
Scheduling jobs one after another on a single machine
Multiple
Machines
Multiple Parallel
Machines
Scheduling jobs on two or more identical machines working in parallel; each job can be assigned to any machine
Flow ShopAll jobs follow the same sequence of machines; each job visits every machine in the same order
Job ShopEach job has its own specific sequence of machines to follow

291.6.1. Single Machine Serial Production

Notation

𝑧𝑖𝑗={1if job𝑗is before job𝑖0otherwise

Job Order Constraints

We must ensure that either job 𝑖 comes before job 𝑗 or vice versa, but not both

If job 𝑖 is before job 𝑗 (𝑧𝑖𝑗=0)

𝑥𝑗𝑥𝑖+𝑝𝑗

If job 𝑗 is before job 𝑖 (𝑧𝑖𝑗=1):

𝑥𝑖𝑥𝑗+𝑝𝑖

These two cases can be rewritten using a big-𝑀 formulation:

𝑥𝑖+𝑝𝑗𝑥𝑗𝑀𝑧𝑖𝑗𝑥𝑗+𝑝𝑖𝑥𝑖𝑀(1𝑧𝑖𝑗)

Where:

Complete Formulation

min𝑗𝐽𝑥𝑗𝑠.𝑡.𝑥𝑖+𝑝𝑗𝑥𝑗𝑀𝑧𝑖𝑗𝑖𝐽,𝑗𝐽,𝑖<𝑗𝑥𝑗+𝑝𝑖𝑥𝑖𝑀(1𝑧𝑖𝑗)𝑖𝐽,𝑗𝐽,𝑖<𝑗𝑥𝑗𝑝𝑗𝑗𝐽𝑥𝑗0𝑗𝐽𝑧𝑖𝑗{0,1}𝑖𝐽,𝑗𝐽,𝑖<𝑗

Interpretation

If jobs are run in order 1,2,,𝑛, the completion times are:

𝑥1=𝑝1𝑥2=𝑝1+𝑝2𝑥3=𝑝1+𝑝2+𝑝3𝑥𝑛=𝑖=1𝑛𝑝𝑖

So, the completion time of a job depends on the sum of processing times of all jobs before it in the schedule

Optional: Release Times

If jobs have release times 𝑅𝑗, meaning each job 𝑗 cannot start before 𝑅𝑗, then we add:

𝑥𝑗𝑅𝑗+𝑝𝑗𝑗𝐽

291.6.2. Multiple Parallel Machines

We aim to assign jobs to machines such that:

Notation

𝑥𝑖𝑗={1if job𝑗is assigned to machine𝑖0otherwise𝐶𝑖=𝑗𝐽𝑝𝑗𝑥𝑖𝑗𝑤𝑗𝐽𝑝𝑗𝑥𝑖𝑗𝑖𝐼

Objective

Minimize makespan 𝑤

Complete Formulation

min𝑤𝑠.𝑡.𝑗𝐽𝑝𝑗𝑥𝑖𝑗𝑤𝑖𝐼𝑖𝐼𝑥𝑖𝑗=1𝑗𝐽𝑥𝑖𝑗{0,1}𝑖𝐼,𝑗𝐽
model.py
from pyomo.environ import *

model = AbstractModel()

model.MACHINES = Set()
model.JOBS = Set()

model.p = Param(model.JOBS, within=PositiveReals)

model.x = Var(model.MACHINES, model.JOBS, domain=Binary)
model.w = Var(domain=NonNegativeReals)

def obj_rule(m):
    return m.w
model.Obj = Objective(rule=obj_rule, sense=minimize)

def job_assignment_rule(m, j):
    return sum(m.x[i, j] for i in m.MACHINES) == 1
model.JobAssignment = Constraint(model.JOBS, rule=job_assignment_rule)

def machine_completion_rule(m, i):
    return sum(m.p[j] * m.x[i, j] for j in m.JOBS) <= m.w
model.MachineCompletion = Constraint(model.MACHINES, rule=machine_completion_rule)

291.6.3. Flow Shop

291.6.4. Job Shop

291.7. Vehicle Routing

291.7.1. Traveling Salesperson

𝑥𝑖𝑗={1if(𝑖,𝑗)is selected0otherwise

Objective

Minimize the total travel cost

min(𝑖,𝑗)𝐸𝑑𝑖𝑗𝑥𝑖𝑗

Constraints

  1. Flow Balancing

For node 𝑘𝑉

𝑖𝑉,𝑖𝑘=1𝑗𝑉,𝑗𝑘=1
  1. Subtours

a. Miller-Tucker-Zemlin (MTZ)

𝑢1=12𝑢𝑖𝑛𝑖𝑉\{1}𝑢𝑖𝑢𝑗+1(𝑛1)(1𝑥𝑖𝑗)(𝑖,𝑗)𝐸,𝑖1,𝑗1

b. Subtour Elimination Constraint (SEC)

For every subset 𝑆𝑉 with |𝑆|2:

𝑖𝑆,𝑗𝑆𝑖𝑗𝑥𝑖𝑗|𝑆|1𝑆𝑉,|𝑆|2

This ensures that no subset of nodes forms a closed tour independent of the main tour.

Complete Formulation

min(𝑖,𝑗)𝐸𝑑𝑖𝑗𝑥𝑖𝑗𝑠.𝑡.𝑖𝑉,𝑖𝑘=1𝑘𝑉𝑗𝑉,𝑗𝑘=1𝑘𝑉𝑥𝑖𝑗{0,1}(𝑖,𝑗)𝐸MTZ or SEC