Optimization: Network Flow Problems

Hongtao Hao / 2023-03-17


Images in this posst came from the slides of CS524 at UW-Madison , 2023Spring

This notebook runs in Julia and is rendered by Hupyter .

Sailco, a simple example #

Sailco manufactures sailboats. During the next 4 months the company must meet the following demands for their sailboats:

Month 1 2 3 4
Number of boats 40 60 70 25

At the beginning of Month 1, Sailco has 10 boats in inventory. Each month it must determine how many boats to produce. During any month, Sailco can produce up to 40 boats with regular labor and an unlimited number of boats with overtime labor. Boats produced with regular labor cost 400 USD each to produce, while boats produced with overtime labor cost 450 USD each. It costs 20 USD to hold a boat in inventory from one month to the next. Find the production and inventory schedule that minimizes the cost of meeting the next 4 months’ demands

using JuMP, HiGHS, DataStructures, NamedArrays

Solution #

m = Model(HiGHS.Optimizer)

demands = [40, 60, 70, 25]

# boats built with regular labor
@variable(m, 0 <= x[1:4] <= 40)
# boats built with overtime labor
@variable(m, y[1:4] >= 0)
# inventory
@variable(m, h[1:5] >= 0)

# for each month
for i in range(1,4)
#     boats plus the inventory must be larger than the demand
#     @constraint(m, x[i] + y[i] + h[i] >= demands[i])
    
    # inventory next month is equal to all boats available minus the demand of that month
    @constraint(m, h[i+1] == h[i] + x[i] + y[i] - demands[i])
end

# first month inventory is 10
@constraint(m, h[1] == 10)

# minimize the total cost
@objective(m, Min, 400*sum(x) + 450*sum(y) + 20*sum(h))

optimize!(m)
println("Solver terminated with status ", termination_status(m))
Running HiGHS 1.4.2 [date: 1970-01-01, git hash: f797c1ab6]
Copyright (c) 2022 ERGO-Code under MIT licence terms
Presolving model
4 rows, 12 cols, 15 nonzeros
4 rows, 12 cols, 15 nonzeros
Presolve : Reductions: rows 4(-1); columns 12(-1); elements 15(-2)
Solving the presolved LP
Using EKK dual simplex solver - serial
  Iteration        Objective     Infeasibilities num(sum)
          0     2.0000000000e+02 Pr: 4(185) 0s
          5     7.6400000000e+04 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Simplex   iterations: 5
Objective value     :  7.6400000000e+04
HiGHS run time      :          0.00
Solver terminated with status OPTIMAL
println("Boats built with regular labor each month: ", Array{Int}(value.(x)))
println("Boats built with overtime labor each month: ", Array{Int}(value.(y)))
println("Inventory: ", Array{Int}(value.(h)))
println("Minimum cost: \`$", objective_value(m))
Boats built with regular labor each month: [40, 40, 40, 25]
Boats built with overtime labor each month: [0, 10, 30, 0]
Inventory: [10, 10, 0, 0, 0]
Minimum cost: $`76400.0

In above,

# for each month
for i in range(1,4)
    # inventory next month is equal to all boats available minus the demand of that month
    @constraint(m, h[i+1] == h[i] + x[i] + y[i] - demands[i])
end

can be rewritten as

@constraint(m, flows[i in 1:4], h[i] + x[i] + y[i] == demands[i] + h[i])

Minimum-cost flow problem #

Lumber transportation problem (J. Reeb and S. Leavengood) #

Millco has three wood mills and is planning three new logging sites. Each mill has a maximum capacity and each logging site can harvest a certain number of truckloads of lumber per day. The cost of a haul is 2 USD/mile of distance. If distances from logging sites to mills are given below, how should the hauls be routed to minimize hauling costs while meeting all demands?

Logging Site Mill A Mill B Mill C Max loads per day per logging site
1 8 15 50 20
2 10 17 20 30
3 30 26 15 45
Mill demand (truckloads/day) 30 35 30
m = Model(HiGHS.Optimizer)

sites = [1, 2, 3]
mills = [:A, :B, :C]

cost_per_haul = 4

supply = Dict(zip(sites, [20, 30, 45]))
demand = Dict(zip(mills, [30, 35, 30]))

dist = NamedArray([8 15 50; 10 17 20; 30 26 15], (sites, mills), ("Sites", "Mills"))
3×3 Named Matrix{Int64}
Sites ╲ Mills │ :A  :B  :C
──────────────┼───────────
1             │  8  15  50
2             │ 10  17  20
3             │ 30  26  15
@variable(m, x[sites, mills] >= 0)

@constraint(m, sup[i in sites], sum(x[i,j] for j in mills) == supply[i])
@constraint(m, dem[j in mills], sum(x[i,j] for i in sites) == demand[j])
@objective(m, Min, cost_per_haul * sum( x[i,j]*dist[i,j] for i in sites, j in mills ))

optimize!(m)
Running HiGHS 1.4.2 [date: 1970-01-01, git hash: f797c1ab6]
Copyright (c) 2022 ERGO-Code under MIT licence terms
Presolving model
6 rows, 9 cols, 18 nonzeros
5 rows, 9 cols, 15 nonzeros
Presolve : Reductions: rows 5(-1); columns 9(-0); elements 15(-3)
Solving the presolved LP
Using EKK dual simplex solver - serial
  Iteration        Objective     Infeasibilities num(sum)
          0     0.0000000000e+00 Pr: 5(155) 0s
          5     5.7600000000e+03 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Simplex   iterations: 5
Objective value     :  5.7600000000e+03
HiGHS run time      :          0.00
NamedArray(Int[value(x[i,j]) for i in sites, j in mills], (sites, mills), ("Sites", "Mills"))
3×3 Named Matrix{Int64}
Sites ╲ Mills │ :A  :B  :C
──────────────┼───────────
1             │  0  20   0
2             │ 30   0   0
3             │  0  15  30

Swim relay problem (Van Roy and Mason) #

The coach of a swim team needs to assign swimmers to a 200-yard medley relay team to compete in a tournament. The problem is that his best swimmers are good in more than one stroke, so it’s not clear which swimmer to assign to which stroke. Here are the best times for each swimmer:

Stroke Carl Chris David Tony Ken
Backstroke 37.7 32.9 33.8 37.0 35.4
Breaststroke 43.4 33.1 42.2 34.7 41.8
Butterfly 33.3 28.5 38.9 30.4 33.6
Freestyle 29.2 26.4 29.6 28.5 31.1

This can be considered as a transportation problem like the Lumber transportation problem we talked about above.

m = Model(HiGHS.Optimizer)

strokes = [:ackstroke, :breaststroke, :butterfly, :freestyle]
persons = [:carl, :chris, :david, :tony, :ken]

speed = NamedArray(
    [
        37.7 32.9 33.8 37 35.4; 
        43.4 33.1 42.2 34.7 41.8;
        33.3 28.5 38.9 30.4 33.6;
        29.2 26.4 29.6 28.5 31.1 
        ], (strokes, persons), ("Stroke", "Person"))

supply = Dict(zip(strokes, [1,1,1,1]))

@variable(m, x[strokes, persons] >= 0)

# each person should swim at most once
@constraint(m, swimmer[p in persons], sum(x[s, p] for s in strokes) <= 1)

# each stroke should be assigned to exactly one person
@constraint(m, assignment[s in strokes], sum(x[s, p] for p in persons) == 1)

# minimize the time consumed
@objective(m, Min, sum(x[s, p] * speed[s,p] for s in strokes, p in persons))

optimize!(m)
Running HiGHS 1.4.2 [date: 1970-01-01, git hash: f797c1ab6]
Copyright (c) 2022 ERGO-Code under MIT licence terms
Presolving model
9 rows, 20 cols, 40 nonzeros
9 rows, 20 cols, 40 nonzeros
Presolve : Reductions: rows 9(-0); columns 20(-0); elements 40(-0) - Not reduced
Problem not reduced by presolve: solving the LP
Using EKK dual simplex solver - serial
  Iteration        Objective     Infeasibilities num(sum)
          0     0.0000000000e+00 Pr: 4(4) 0s
          8     1.2620000000e+02 Pr: 0(0) 0s
Model   status      : Optimal
Simplex   iterations: 8
Objective value     :  1.2620000000e+02
HiGHS run time      :          0.00
NamedArray([value(x[s,p]) for s in strokes, p in persons], (strokes, persons), ("Stroke", "Person"))
4×5 Named Matrix{Float64}
Stroke ╲ Person │  :carl  :chris  :david   :tony    :ken
────────────────┼───────────────────────────────────────
:ackstroke      │    0.0     0.0     1.0     0.0     0.0
:breaststroke   │    0.0     0.0     0.0     1.0     0.0
:butterfly      │    0.0     1.0     0.0    -0.0     0.0
:freestyle      │    1.0    -0.0     0.0     0.0     0.0
#Optimization

Last modified on 2023-03-17