287. Linear Programming

287.1. Mathematical Programs

287.1.1. Formulation


min𝑓(𝑥1,𝑥2,,𝑥𝑛)(objective function)𝑠.𝑡.𝑔𝑖(𝑥1,𝑥2,,𝑥𝑛)𝑏𝑖𝑖1,,𝑚(constraints)𝑥𝑗𝑗1,,𝑛(decision variables)

min𝑗=1𝑛𝑐𝑗𝑥𝑗𝑠.𝑡.𝑗=1𝑛𝐴𝑖𝑗𝑥𝑗𝑏𝑖𝑖=1,,𝑚

Vector

min𝑐𝑇𝑥𝑠.𝑡.𝑎𝑖𝑇𝑥𝑏𝑖𝑖=1,,𝑚

Matrix

min𝑐𝑇𝑥𝑠.𝑡.𝐴𝑥𝑏𝐴=[𝑎11𝑎12𝑎1𝑛𝑎21𝑎22𝑎2𝑛𝑎𝑚1𝑎𝑚2𝑎𝑚𝑛]𝑥=[𝑥1𝑥2𝑥𝑛]𝑐=[𝑐1𝑐2𝑐𝑛]𝑏=[𝑏1𝑏2𝑏𝑛]𝐴𝑥=[𝑎11𝑎12𝑎1𝑛𝑎21𝑎22𝑎2𝑛𝑎𝑚1𝑎𝑚2𝑎𝑚𝑛][𝑥1𝑥2𝑥𝑛]=[𝑎11𝑥1𝑎12𝑥2𝑎1𝑛𝑥𝑛𝑎21𝑥1𝑎22𝑥2𝑎2𝑛𝑥𝑛𝑎𝑚1𝑥1𝑎𝑚2𝑥2𝑎𝑚𝑛𝑥𝑛][𝑏1𝑏2𝑏𝑛]𝑐𝑇𝑥=[𝑐1,𝑐2,,𝑐𝑛][𝑥1𝑥2𝑥𝑛]=𝑗=1𝑛𝑐𝑗𝑥𝑗

287.1.2. Transformation


max𝑓(𝑥)min𝑓(𝑥)𝑔𝑖(𝑥)𝑏𝑖𝑔𝑖(𝑥)𝑏𝑖𝑔𝑖(𝑥)=𝑏𝑖𝑔𝑖(𝑥)𝑏𝑖𝑔𝑖(𝑥)𝑏𝑖
max𝑥1𝑥2𝑠.𝑡.2𝑥1+𝑥23𝑥1+4𝑥2=5min𝑥1+𝑥2𝑠.𝑡.2𝑥1𝑥23𝑥1+4𝑥25𝑥1+4𝑥25

287.1.3. Constraints


𝑥𝑖0or𝑥𝑖0𝑖=1𝑛𝑐𝑖𝑥𝑖𝑏𝑖

287.1.4. Feasible Solutions

287.1.5. Feasible Region and Optimal Solution

287.1.6. Binding Constraint

Let 𝑔()𝑏 be an inequality constraint and |𝑥| be a solution. 𝑔()𝑏 is binding at |𝑥| if 𝑔(|𝑥|)=𝑏

Example
2𝑥1+𝑥2100is𝐛𝐢𝐧𝐝𝐢𝐧𝐠 (𝐚𝐜𝐭𝐢𝐯𝐞)at the point(𝑥1,𝑥2)=(30,40)2𝑥1+𝑥2100is𝐧𝐨𝐧-𝐛𝐢𝐧𝐝𝐢𝐧𝐠 (𝐢𝐧𝐚𝐜𝐭𝐢𝐯𝐞)at the point(𝑥1,𝑥2)=(20,20)

287.1.7. Strict and Weak Inequalities

287.1.8. Graphical Approach

Example
max40𝑥1+30𝑥2𝑠.𝑡.2𝑥1+𝑥2100𝑥1+2𝑥280

287.1.9. Types of LP

287.1.10. LP Formulation

Example

Production

We produce desks and tables

  • Producing a desk requires 3 units of wood, 1 hour of labor and 50 minutes of machine time
  • Producing a table requires 5 units of wood, 2 hours of labor and 20 minutes of machine time

For each day, we have

  • 200 workers that each work 8 hours
  • 50 machines that each run for 16 hours
  • A supply of 3600 units of wood

Desks and tables are sold $700 and $900 per unit, respectively

Everything that is produced is sold

1. Define Variables

𝑥1: number of desks produced per day

𝑥2: number of tables produced per day

2. Formulate Objective Function

max700𝑥1+900𝑥2

3. Formulate Constraints

ResourceConsumption perTotal supply
DeskTable
Wood3 units5 units3600 units
Labor1 hours2 hours200 workers × 8 hours
= 1600 hours
Machine
time
50 minutes20 minutes50 machines × 16 hours
= 800 hours


3𝑥1+5𝑥23600𝑥1+2𝑥2160050𝑥1+20𝑥248000

4. Complete Formulation

max700𝑥1+900𝑥2𝑠.𝑡.3𝑥1+5𝑥23600(wood)𝑥1+2𝑥21600(labor)50𝑥1+20𝑥248000(machine)𝑥10𝑥20

Compact Formulation

max𝑗=1𝑛𝑃𝑗𝑥𝑗𝑠.𝑡.𝑗=1𝑛𝐴𝑖𝑗𝑥𝑗𝑅𝑖𝑖=1,,𝑚𝑥𝑗0𝑗=1,,𝑛

Where:

  • 𝑛: number of products
  • 𝑚: number of resources
  • 𝑗: indices of products
  • 𝑖: indices of resources
  • 𝑃𝑗: unit sale price of product 𝑗
  • 𝑅𝑖: supply limit of resource 𝑖
  • 𝐴𝑖𝑗: unit of resource 𝑖 to produce one unit of product 𝑗
  • 𝑖=1,,𝑚
  • 𝑗=1,,𝑛
  • 𝑥𝑗: production quantity for product 𝑗, 𝑗=1,,𝑛
model.py
from pyomo.environ import *
from pyomo.dataportal import DataPortal

model = AbstractModel()

# Sets
model.Products = Set()
model.Resources = Set()

# Parameters
model.Profit = Param(model.Products)
model.Supply = Param(model.Resources)
model.Consumption = Param(model.Resources, model.Products)

# Variables
model.x = Var(model.Products, domain=NonNegativeReals)

# Objective: Maximize profit
def objective_rule(model):
    return sum(model.Profit[j] * model.x[j] for j in model.Products)
model.OBJ = Objective(rule=objective_rule, sense=maximize)

# Constraints: Do not exceed resource supply
def constraint_rule(model, i):
    return sum(model.Consumption[i, j] * model.x[j] for j in model.Products) <= model.Supply[i]
model.ResourceConstraint = Constraint(model.Resources, rule=constraint_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 Products := Desk Table ;
set Resources := Wood Labor Machine ;

param Profit :=
Desk   700
Table  900 ;

param Supply :=
Wood    3600
Labor   1600
Machine 48000 ;

param Consumption:
          Desk Table :=
Wood        3     5
Labor       1     2
Machine    50    20 ;

Output:

  Variables:
  x : Size=2, Index=Products
      Key   : Lower : Value            : Upper : Fixed : Stale : Domain
      Desk :     0 : 884.210526315789 :  None : False : False : NonNegativeReals
      Table :     0 : 189.473684210526 :  None : False : False : NonNegativeReals

Objectives:
  OBJ : Size=1, Index=None, Active=True
      Key  : Active : Value
      None :   True : 789473.6842105257

Constraints:
  ResourceConstraint : Size=3
      Key     : Lower : Body              : Upper
        Labor :  None : 1263.157894736841 :  1600.0
      Machine :  None : 47999.99999999997 : 48000.0
        Wood :  None : 3599.999999999997 :  3600.0

)

Example

Multi Period (Inventory)

We produce and sell products

For the coming 4 days, the demand will be

  • Days 1: 100
  • Days 2: 150
  • Days 3: 200
  • Days 4: 170

The unit production costs are different for different days

  • Days 1: $9
  • Days 2: $12
  • Days 3: $10
  • Days 4: $12

Prices are all fixed. So maximizing profit is equivalent to minimizing costs.

We may store products and sell them later. Inventory cost is $1 per unit per day.

E.g., producing 620 units on day 1 to fulfill all demands:

$9×620+$1×150+$2×200+$3×170=$6,640
𝐼𝑡1+𝑥𝑡𝑑𝑡=𝐼𝑡
  • 𝐼𝑡1: inevntory at the end of day 𝑡1 (i.e., beginning inventory for day 𝑡)

  • 𝑥𝑡: units produced on day 𝑡

  • 𝑑𝑡: demand (or sales) on dat 𝑡

  • 𝐼𝑡: inventory at the end of day 𝑡

Inventory costs are calculated according to ending inventory

  • 𝑥𝑡: production quantity of day 𝑡, 𝑡=1,,4

  • 𝑦𝑡: ending inventory of day 𝑡, 𝑡=1,,4

Objective function:

min9𝑥1+12𝑥2+10𝑥3+12𝑥4+𝑦1+𝑦2+𝑦3+𝑦4

Inventory balancing constraints:

  • Day 1: 𝑥1100=𝑦1

  • Day 2: 𝑦1+𝑥2150=𝑦2

  • Day 3: 𝑦2+𝑥3200=𝑦3

  • Day 4: 𝑦3+𝑥4170=𝑦4

Demand fulfillment constraints:

  • 𝑥1100

  • 𝑦1+𝑥2150

  • 𝑦2+𝑥3200

  • 𝑦3+𝑥4170

Non-negativity constraints:

  • 𝑦𝑡0

  • 𝑥𝑡0

Complete Formulation

min9𝑥1+12𝑥2+10𝑥3+12𝑥4+𝑦1+𝑦2+𝑦3+𝑦4𝑠.𝑡.𝑥1100=𝑦1𝑦1+𝑥2150=𝑦2𝑦2+𝑥3200=𝑦3𝑦3+𝑥4170=𝑦4𝑥1100𝑦1+𝑥2150𝑦2+𝑥3200𝑦3+𝑥4170𝑥𝑡,𝑦𝑡0𝑡=1,,4

Simplification (Redundant Constraints)

  • Inventory balancing & non-negativity imply fulfillment

    • E.g., in day 1, 𝑥1100=𝑦1 and 𝑦10 means 𝑥1100
min9𝑥1+12𝑥2+10𝑥3+12𝑥4+𝑦1+𝑦2+𝑦3+𝑦4𝑠.𝑡.𝑥1100=𝑦1𝑦1+𝑥2150=𝑦2𝑦2+𝑥3200=𝑦3𝑦3+𝑥4170=𝑦4𝑥𝑡,𝑦𝑡0𝑡=1,,4
  • No need to have ending inventory in period 4 (costly but useless)
min9𝑥1+12𝑥2+10𝑥3+12𝑥4+𝑦1+𝑦2+𝑦3+𝑦4𝑠.𝑡.𝑥1100=𝑦1𝑦1+𝑥2150=𝑦2𝑦2+𝑥3200=𝑦3𝑦3+𝑥4170=0𝑥𝑡0𝑡=1,,4𝑦𝑡0𝑡=1,,3

Compact Formulation

min𝑡=14(𝐶𝑡𝑥𝑡+𝑦𝑡)𝑠.𝑡.𝑦𝑡1+𝑥𝑡𝐷𝑡=𝑦𝑡𝑡=1,,4𝑦0=0𝑥𝑡,𝑦𝑡0𝑡=1,,4

Where:

  • 𝐷𝑡: demand on day 𝑡
  • 𝑦𝑡: ending inventory of day 𝑡
  • 𝐶𝑡: unit production cost on day 𝑡
Example

Personnel Scheduling

Scheduling employees

Each employee must work for 5 consecutive days and then take 2 consecutive rest days

Number of employees required for each day

MonTueWedThuFriSatSun
110801503070160120

Seven shifts

  • Mon to Fri
  • Tue to Sat

Minimize number of employees hired

Let 𝑥𝑖 be the number of employees assigned to work from day 𝑖 for 5 consecutive days

Objective function:

min𝑥1+𝑥2+𝑥3+𝑥4+𝑥5+𝑥6+𝑥7

Demand Fullfilment Constraints

  • 110 employeeds needed Monday
𝑥1+𝑥4+𝑥5+𝑥6+𝑥7110

An employee that works on Tues (𝑡2) or Wed (𝑡3) does not work on Mon (𝑡1)

  • 80 employees needed Tuesday
𝑥1+𝑥2+𝑥5+𝑥6+𝑥780

An employee that works on Wed (𝑡3) or Thu (𝑡4) does not work on Tue (𝑡2)

  • 120 employees needed Sunday
𝑥3+𝑥4+𝑥5+𝑥6+𝑥7120

An employee that works on Mon (𝑡1) or Tue (𝑡2) does not work on Sun (𝑡7)

Non-Nagativity Constraints

𝑥𝑖0𝑖=𝑖,,7

Complete Formulation

min𝑥1+𝑥2+𝑥3+𝑥4+𝑥5+𝑥6+𝑥7𝑠.𝑡.𝑥1+𝑥4+𝑥5+𝑥6+𝑥7110𝑥1+𝑥2+𝑥5+𝑥6+𝑥780𝑥1+𝑥2+𝑥3+𝑥6+𝑥7150𝑥1+𝑥2+𝑥3+𝑥4+𝑥730𝑥1+𝑥2+𝑥3+𝑥4+𝑥570𝑥2+𝑥3+𝑥4+𝑥5+𝑥6160𝑥3+𝑥4+𝑥5+𝑥6+𝑥7120𝑥𝑖0𝑖=1,,7
model.py
from pyomo.environ import *
from pyomo.dataportal import DataPortal

model = AbstractModel()

# Sets
model.Days = Set(ordered=True)           # Days 1..7 (Mon..Sun)
model.Shifts = Set(ordered=True)         # Shifts starting on days 1..7

# Parameters
model.Demand = Param(model.Days)         # Daily staffing requirements
model.Cover = Param(model.Days, model.Shifts, within=Binary)  # Coverage matrix (1 if shift j covers day i)

# Variables
model.x = Var(model.Shifts, domain=NonNegativeReals)

# Objective: Minimize total employees hired
def obj_rule(model):
    return sum(model.x[s] for s in model.Shifts)
model.OBJ = Objective(rule=obj_rule, sense=minimize)

# Constraints: Cover daily demand
def demand_rule(model, d):
    return sum(model.Cover[d, s] * model.x[s] for s in model.Shifts) >= model.Demand[d]
model.DemandConstraint = Constraint(model.Days, 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 Days := Mon Tue Wed Thu Fri Sat Sun ;
set Shifts := s1 s2 s3 s4 s5 s6 s7 ;

param Demand :=
Mon 110
Tue 80
Wed 150
Thu 30
Fri 70
Sat 160
Sun 120 ;

# Each shift covers 5 consecutive days starting on the shift's day
# 1 if shift s_j covers day d_i, 0 otherwise

param Cover:
        s1 s2 s3 s4 s5 s6 s7 :=
Mon     1  0  0  1  1  1  1
Tue     1  1  0  0  1  1  1
Wed     1  1  1  0  0  1  1
Thu     1  1  1  1  0  0  1
Fri     1  1  1  1  1  0  0
Sat     0  1  1  1  1  1  0
Sun     0  0  1  1  1  1  1 ;

Output:

  Variables:
  x : Size=7, Index=Shifts
      Key : Lower : Value            : Upper : Fixed : Stale : Domain
        s1 :     0 : 3.33333333333333 :  None : False : False : NonNegativeReals
        s2 :     0 :             40.0 :  None : False : False : NonNegativeReals
        s3 :     0 : 13.3333333333333 :  None : False : False : NonNegativeReals
        s4 :     0 :              0.0 :  None : False : False : NonNegativeReals
        s5 :     0 : 13.3333333333333 :  None : False : False : NonNegativeReals
        s6 :     0 : 93.3333333333333 :  None : False : False : NonNegativeReals
        s7 :     0 :              0.0 :  None : False : False : NonNegativeReals

Objectives:
  OBJ : Size=1, Index=None, Active=True
      Key  : Active : Value
      None :   True : 163.33333333333323

Constraints:
  DemandConstraint : Size=7
      Key : Lower : Body               : Upper
      Fri :  70.0 :  69.99999999999993 :  None
      Mon : 110.0 : 109.99999999999993 :  None
      Sat : 160.0 :  159.9999999999999 :  None
      Sun : 120.0 :  119.9999999999999 :  None
      Thu :  30.0 :  56.66666666666663 :  None
      Tue :  80.0 : 149.99999999999994 :  None
      Wed : 150.0 : 149.99999999999994 :  None