Skip to content

Commit fe30213

Browse files
committed
implement minimum cost flow
1 parent 218a8cf commit fe30213

File tree

5 files changed

+218
-42
lines changed

5 files changed

+218
-42
lines changed

src/OperationsResearchModels.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import .ShortestPath: ShortestPathResult, ShortestPathProblem
5959

6060
import .Network: Connection, nodes
6161
import .MaximumFlow: MaximumFlowResult, MaximumFlowProblem
62+
import .MaximumFlow: MinimumCostFlowProblem, MinimumCostFlowResult
6263
import .Assignment: AssignmentProblem, AssignmentResult
6364
import .Game: game, GameResult, game_solver
6465
import .MinimumSpanningTree: hasloop, MstResult, MstProblem
@@ -73,8 +74,8 @@ import .TravelingSalesman: TravelinSalesmenResult, travelingsalesman
7374
import .Simplex: SimplexProblem, simplexiterations, createsimplexproblem, gaussjordan
7475

7576
export TransportationProblem, TransportationResult, balance, isbalanced, northwestcorner, leastcost
76-
export Connection, ShortestPathResult, MaximumFlowResult, nodes
77-
export ShortestPathProblem, MaximumFlowProblem
77+
export Connection, ShortestPathResult, MaximumFlowResult, MinimumCostFlowResult, nodes
78+
export ShortestPathProblem, MaximumFlowProblem, MinimumCostFlowProblem
7879
export AssignmentProblem, AssignmentResult
7980
export game, GameResult, game_solver
8081
export hasloop, MstResult, MstProblem

src/maximumflow.jl

Lines changed: 162 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ using JuMP, HiGHS
66
import ..OperationsResearchModels: solve
77

88

9-
export MaximumFlowProblem
10-
export MaximumFlowResult
9+
export MaximumFlowProblem, MaximumFlowResult
10+
export MinimumCostFlowProblem, MinimumCostFlowResult
1111

1212

1313
struct MaximumFlowResult
@@ -19,6 +19,66 @@ struct MaximumFlowProblem
1919
connections::Vector{Connection}
2020
end
2121

22+
struct MinimumCostFlowProblem
23+
connections::Vector{Connection}
24+
costs::Vector{Connection}
25+
end
26+
27+
struct MinimumCostFlowResult
28+
path::Vector{Connection}
29+
cost::Float64
30+
end
31+
32+
33+
function leftexpressions(x::Matrix{JuMP.VariableRef}, node::Int64, nodes::Vector{Connection}, model)
34+
lst = []
35+
for conn in nodes
36+
if conn.to == node
37+
push!(lst, conn)
38+
end
39+
end
40+
if length(lst) == 0
41+
return :f
42+
end
43+
expr = @expression(model, 0)
44+
for i = eachindex(lst)
45+
expr += x[lst[i].from, lst[i].to]
46+
end
47+
return expr
48+
end
49+
50+
function rightexpressions(x::Matrix{JuMP.VariableRef}, node::Int64, nodes::Vector{Connection}, model)
51+
lst = []
52+
for conn in nodes
53+
if conn.from == node
54+
push!(lst, conn)
55+
end
56+
end
57+
if length(lst) == 0
58+
return :f
59+
end
60+
expr = @expression(model, 0)
61+
for i = eachindex(lst)
62+
expr += x[lst[i].from, lst[i].to]
63+
end
64+
return expr
65+
end
66+
67+
68+
function hassameorder(a::Vector{Connection}, b::Vector{Connection})::Bool
69+
70+
if length(a) != length(b)
71+
return false
72+
end
73+
74+
for i = 1:length(a)
75+
if a[i].from != b[i].from || a[i].to != b[i].to
76+
return false
77+
end
78+
end
79+
80+
return true
81+
end
2282

2383
"""
2484
@@ -65,45 +125,10 @@ julia> result.flow
65125
7.0
66126
```
67127
"""
68-
function solve(problem::MaximumFlowProblem)
128+
function solve(problem::MaximumFlowProblem)::MaximumFlowResult
69129

70130
cns = problem.connections
71131

72-
function leftexpressions(node::Int64, nodes::Vector{Connection}, model)
73-
lst = []
74-
for conn in nodes
75-
if conn.to == node
76-
push!(lst, conn)
77-
end
78-
end
79-
if length(lst) == 0
80-
return :f
81-
end
82-
expr = @expression(model, 0)
83-
for i = eachindex(lst)
84-
expr += x[lst[i].from, lst[i].to]
85-
end
86-
return expr
87-
end
88-
89-
function rightexpressions(node::Int64, nodes::Vector{Connection}, model)
90-
lst = []
91-
for conn in nodes
92-
if conn.from == node
93-
push!(lst, conn)
94-
end
95-
end
96-
if length(lst) == 0
97-
return :f
98-
end
99-
expr = @expression(model, 0)
100-
for i = eachindex(lst)
101-
expr += x[lst[i].from, lst[i].to]
102-
end
103-
return expr
104-
end
105-
106-
107132
model = Model(HiGHS.Optimizer)
108133
MOI.set(model, MOI.Silent(), true)
109134

@@ -122,8 +147,8 @@ function solve(problem::MaximumFlowProblem)
122147

123148
# Constraints
124149
for nextnode in mynodes
125-
leftexpr = leftexpressions(nextnode, cns, model)
126-
rightexpr = rightexpressions(nextnode, cns, model)
150+
leftexpr = leftexpressions(x, nextnode, cns, model)
151+
rightexpr = rightexpressions(x, nextnode, cns, model)
127152
if leftexpr == :f
128153
@constraint(model, rightexpr == f)
129154
elseif rightexpr == :f
@@ -157,4 +182,101 @@ end
157182

158183

159184

185+
"""
186+
solve(problem, flow)
187+
188+
# Description
189+
190+
This function solves the Minimum Cost Flow problem given a flow value.
191+
192+
# Arguments
193+
194+
- `problem::MinimumCostFlowProblem`: The problem in type of MinimumCostFlowProblem
195+
- `flow::Float64`: The flow value to be used in the problem.
196+
197+
198+
"""
199+
function solve(problem::MinimumCostFlowProblem, flow::Float64)::MinimumCostFlowResult
200+
cns = problem.connections
201+
costs = problem.costs
202+
203+
if !hassameorder(cns, costs)
204+
throw(ArgumentError("Connections and costs must have the same order."))
205+
end
206+
207+
model = Model(HiGHS.Optimizer)
208+
MOI.set(model, MOI.Silent(), true)
209+
210+
mynodes = nodes(cns)
211+
n = length(mynodes)
212+
213+
startnode = start(cns)
214+
finishnode = finish(cns)
215+
216+
# Variables
217+
@variable(model, x[1:n, 1:n] .>= 0)
218+
219+
# Objective Function
220+
@objective(model, Max, sum(x[conn.from, conn.to] * conn.value for conn in costs))
221+
222+
# Constraints
223+
for nextnode in mynodes
224+
leftexpr = leftexpressions(x, nextnode, cns, model)
225+
rightexpr = rightexpressions(x, nextnode, cns, model)
226+
if leftexpr == :f
227+
@constraint(model, rightexpr == flow)
228+
elseif rightexpr == :f
229+
@constraint(model, leftexpr == flow)
230+
else
231+
@constraint(model, leftexpr - rightexpr == 0)
232+
end
233+
end
234+
235+
for nd in cns
236+
@constraint(model, x[nd.from, nd.to] <= nd.value)
237+
end
238+
239+
optimize!(model)
240+
241+
xs = value.(x)
242+
cost = JuMP.objective_value(model)
243+
solutionnodes = []
244+
for i = 1:n
245+
for j = 1:n
246+
if xs[i, j] > 0
247+
push!(solutionnodes, Connection(i, j, xs[i, j]))
248+
end
249+
end
250+
end
251+
252+
return MinimumCostFlowResult(solutionnodes, cost)
253+
end
254+
255+
256+
"""
257+
solve(problem)
258+
259+
# Description
260+
261+
This function solves the Minimum Cost Flow problem by first solving the Maximum Flow problem and
262+
then using the flow value to solve the Minimum Cost Flow problem.
263+
264+
# Arguments
265+
266+
- `problem::MinimumCostFlowProblem`: The problem in type of MinimumCostFlowProblem
267+
268+
"""
269+
function solve(problem::MinimumCostFlowProblem)::MinimumCostFlowResult
270+
271+
maximumflowproblem = MaximumFlowProblem(problem.connections)
272+
273+
maximumflowresult = solve(maximumflowproblem)
274+
275+
f = maximumflowresult.flow
276+
277+
result::MinimumCostFlowResult = solve(problem, f)
278+
279+
return result
280+
end
281+
160282
end # end of module

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ include("testjohnsons.jl")
1212
include("testutility.jl")
1313
include("testshortestpath.jl")
1414
include("testmaximumflow.jl")
15+
include("testminimumcostflow.jl")
1516
include("testgame.jl")
1617
include("testmst.jl")
1718
include("testpmedian.jl")

test/testmaximumflow.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@
4646
end
4747
end
4848
end
49+
50+

test/testminimumcostflow.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
@testset "Minimum Cost Flow" verbose = true begin
2+
@testset "Basic Minimum Cost Flow example" begin
3+
conns = [
4+
Connection(1, 2, 20),
5+
Connection(1, 3, 30),
6+
Connection(2, 4, 10),
7+
Connection(3, 4, 5)
8+
]
9+
10+
costs = [
11+
Connection(1, 2, 1),
12+
Connection(1, 3, 2),
13+
Connection(2, 4, 3),
14+
Connection(3, 4, 3)
15+
]
16+
17+
problem = MinimumCostFlowProblem(conns, costs)
18+
19+
result = solve(problem)
20+
21+
@test result isa MinimumCostFlowResult
22+
@test result.cost == 65.0
23+
end
24+
25+
@testset "Basic Minimum Cost Flow example -2" begin
26+
conns = [
27+
Connection(1, 2, 20),
28+
Connection(1, 3, 30),
29+
Connection(2, 4, 10),
30+
Connection(3, 4, 5),
31+
Connection(3, 2, 25)
32+
]
33+
34+
costs = [
35+
Connection(1, 2, 1),
36+
Connection(1, 3, 2),
37+
Connection(2, 4, 3),
38+
Connection(3, 4, 3),
39+
Connection(3, 2, 2)
40+
]
41+
42+
problem = MinimumCostFlowProblem(conns, costs)
43+
44+
result = solve(problem)
45+
46+
@test result isa MinimumCostFlowResult
47+
@test result.cost == 95.0
48+
49+
end
50+
end

0 commit comments

Comments
 (0)