Skip to content
This repository was archived by the owner on Oct 22, 2021. It is now read-only.

Commit aa8528e

Browse files
ndinsmorematbesancon
authored andcommitted
Significant improvement to mincost_flow (#23)
* Fixed mincost_flow to have much better performace.. around 10000x faster forcases with 500 nodes. Changed mincost_flow arguments to be more consistent with traditional problems. * Made requested fixes to documentation, code spacing, and changed variable names to increase clarity. * Changed argument names to add clarity * Update testing to new interface, maintained testing for depreciated interface in seperate file. * Fix missing test
1 parent eff807b commit aa8528e

File tree

5 files changed

+251
-54
lines changed

5 files changed

+251
-54
lines changed

src/LightGraphsFlows.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import SimpleTraits
88
using MathProgBase.HighLevelInterface: linprog
99
using MathProgBase.SolverInterface: AbstractMathProgSolver
1010

11-
using SparseArrays: spzeros
11+
using SparseArrays: spzeros, sparse, sparsevec
1212
using Markdown: @doc_str
1313

1414
import Base: getindex, size, transpose, adjoint
@@ -26,6 +26,7 @@ include("mincut.jl")
2626

2727
export
2828
maximum_flow, EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm,
29-
multiroute_flow, KishimotoAlgorithm, ExtendedMultirouteFlowAlgorithm, mincost_flow, mincut
29+
multiroute_flow, KishimotoAlgorithm, ExtendedMultirouteFlowAlgorithm, mincost_flow
30+
3031

3132
end

src/mincost.jl

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
"""
2-
mincost_flow(graph, capacity, demand, cost, solver, [, source][, sink])
2+
mincost_flow(graph, node_demand, edge_capacity, edge_cost, solver,<keyword arguments>)
33
4-
Find a flow satisfying the `demand` and `capacity` constraints for each edge
5-
while minimizing the `sum(cost.*flow)`.
4+
Find a flow satisfying the `node_demand` at each node and `edge_capacity` constraints for each edge
5+
while minimizing the `sum(edge_cost.*flow)`.
66
7-
- If `source` and `sink` are specified, they are allowed a net flow production,
8-
consumption respectively. All other nodes must respect the flow conservation
9-
property.
7+
-`node_demand` is a vector of nodal values, which should be positive for sources and
8+
negative at sink nodes, any node with zero demand is a transport node
109
11-
- The problem can be seen as a linear programming problem and uses a LP
10+
- The problem can be seen as a linear programming problem and uses a LP
1211
solver under the hood. We use Clp in the examples and tests.
1312
1413
Returns a flow matrix, flow[i,j] corresponds to the flow on the (i,j) arc.
14+
# Arguments
15+
- `edge_demand::AbstractMatrix`: demand a minimum flow for a given edge.
16+
- `edge_demand_exact=true`: changes the capacity of a non zero edge to the demanded value
17+
- `source_nodes::AbstractVector` Sources at which the nodal netflow is allowed to be greater than nodal demand **
18+
- `sink_nodes::AbstractVector` Sinks at which the nodal netflow is allowed to be less than nodal demand **
19+
** source_nodes & sink_nodes are only needed when nodal flow are not explictly set in node_demand
1520
1621
### Usage Example:
1722
@@ -31,52 +36,87 @@ julia> w[1,3] = 10.
3136
julia> w[1,4] = 5.
3237
julia> w[2,3] = 2.
3338
julia> w[2,4] = 2.
34-
julia> # v2 -> sink have demand of one
35-
julia> demand = spzeros(6,6)
36-
julia> demand[3,6] = 1
37-
julia> demand[4,6] = 1
39+
julia> demand = spzeros(6)
40+
julia> demand[5] = 2
41+
julia> demand[6] = -2
3842
julia> capacity = ones(6,6)
39-
julia> flow = mincost_flow(g, capacity, demand, w, ClpSolver(), 5, 6)
43+
julia> flow = mincost_flow(g, demand, capacity, w, ClpSolver())
4044
```
4145
"""
46+
47+
4248
function mincost_flow(g::lg.DiGraph,
43-
capacity::AbstractMatrix,
44-
demand::AbstractMatrix,
45-
cost::AbstractMatrix,
46-
solver::AbstractMathProgSolver,
47-
source::Int = -1, # if source and/or sink omitted or not in nodes, circulation problem
48-
sink::Int = -1)
49-
flat_cap = collect(Iterators.flatten(capacity))
50-
flat_dem = collect(Iterators.flatten(demand))
51-
flat_cost = collect(Iterators.flatten(cost))
52-
n = lg.nv(g)
53-
b = zeros(n+n*n)
54-
A = spzeros(n+n*n,n*n)
55-
sense = ['=' for _ in 1:n]
56-
append!(sense, ['>' for _ in 1:n*n])
57-
for node in 1:n
58-
if sink == node
59-
sense[sink] = '>'
60-
elseif source == node
61-
sense[source] = '<'
62-
end
63-
col_idx = (node-1)*n+1:node*n
64-
line_idx = node:n:n*n
65-
for jdx in col_idx
66-
A[node,jdx] = A[node,jdx]+1.0
67-
end
68-
for idx in line_idx
69-
A[node,idx] = A[node,idx]-1.0
49+
node_demand::AbstractVector,
50+
edge_capacity::AbstractMatrix,
51+
edge_cost::AbstractMatrix,
52+
solver::AbstractMathProgSolver;
53+
edge_demand::Union{Nothing,AbstractMatrix} = nothing,
54+
edge_demand_exact::Bool = false, #If true changes capacity of that node to the value in edge demand
55+
source_nodes::AbstractVector{<:Integer} = Vector{Integer}(), #Source nodes at which to allow a netflow greater than nodal demand
56+
sink_nodes::AbstractVector{<:Integer} = Vector{Integer}() #Sink nodes at which to allow a netflow less than nodal demand
57+
)
58+
T = eltype(g)
59+
VT = promote_type(eltype(edge_capacity),eltype(node_demand),eltype(edge_cost))
60+
61+
nv = lg.nv(g)
62+
ne = lg.ne(g)
63+
b = Vector{VT}(undef,nv)
64+
AI = Vector{T}(undef,2*ne)
65+
AJ = Vector{T}(undef,2*ne)
66+
AV = Vector{VT}(undef,2*ne)
67+
edge_costs = Vector{VT}(undef,ne)
68+
upperbounds = Vector{VT}(undef,ne)
69+
lowerbounds = Vector{VT}(undef,ne)
70+
sense = ['=' for _ in 1:nv]
71+
72+
has_edge_demand= edge_demand !== nothing
73+
74+
inds = [-1,0]
75+
for (n,e) in enumerate(lg.edges(g))
76+
inds .+= 2
77+
s = lg.src(e)
78+
t = lg.dst(e)
79+
AI[inds] = [s,t] #the node index
80+
AJ[inds] = [n,n] #the edge index
81+
AV[inds] = [1,-1]
82+
upperbounds[n] = edge_capacity[s,t]
83+
lowerbounds[n] = has_edge_demand ? edge_demand[s,t] : zero(VT)
84+
if edge_demand_exact && has_edge_demand && lowerbounds[n] != 0
85+
upperbounds[n] = lowerbounds[n]
7086
end
87+
88+
edge_costs[n] = edge_cost[s,t]
7189
end
72-
for src in 1:n
73-
for dst in 1:n
74-
if lg.Edge(src,dst) lg.edges(g)
75-
A[n+src+n*(dst-1),src+n*(dst-1)] = 1
76-
sense[n+src+n*(dst-1)] = '<'
77-
end
78-
end
90+
for n = 1:nv
91+
b[n] = node_demand[n]
7992
end
80-
sol = linprog(flat_cost, A, sense, b, flat_dem, flat_cap, solver)
81-
[sol.sol[idx + n*(jdx-1)] for idx=1:n,jdx=1:n]
93+
94+
for n in source_nodes
95+
sense[n] = '>'
96+
end
97+
for n in sink_nodes
98+
sense[n] = '<'
99+
end
100+
101+
102+
A = sparse(AI,AJ,AV,nv,ne)
103+
# return (edge_costs, A, sense, b, lowerbounds, upperbounds, solver)
104+
sol = linprog(edge_costs, A, sense, b, lowerbounds, upperbounds, solver)
105+
sol_sparse = sparse(view(AI,1:2:2*ne),view(AI,2:2:2*ne),sol.sol,nv,nv)
106+
return sol_sparse
82107
end
108+
109+
@deprecate mincost_flow(g::lg.DiGraph,
110+
capacity::AbstractMatrix,
111+
demand::AbstractMatrix,
112+
cost::AbstractMatrix,
113+
solver::AbstractMathProgSolver,
114+
source::Int, # if source and/or sink omitted or not in nodes, circulation problem
115+
sink::Int) mincost_flow(g,spzeros(lg.nv(g)),capacity,cost,solver,edge_demand=demand,source_nodes=[source],sink_nodes=[sink])
116+
117+
@deprecate mincost_flow(g::lg.DiGraph,
118+
capacity::AbstractMatrix,
119+
demand::AbstractMatrix,
120+
cost::AbstractMatrix,
121+
solver::AbstractMathProgSolver
122+
) mincost_flow(g,spzeros(lg.nv(g)),capacity,cost,solver,edge_demand=demand)

test/mincost.jl

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ using SparseArrays: spzeros
2525
demand[4,6] = 1
2626
capacity = ones(6,6)
2727

28-
flow = mincost_flow(g, capacity, demand, w, ClpSolver(), 5, 6)
28+
flow = mincost_flow(g, spzeros(lg.nv(g)),capacity, w, ClpSolver(), edge_demand=demand,source_nodes=[5],sink_nodes= [6])
2929
@test flow[5,1] == 1
3030
@test flow[5,2] == 1
3131
@test flow[3,6] == 1
@@ -41,9 +41,66 @@ using SparseArrays: spzeros
4141
@test sum(flow[n1,:]) sum(flow[:,n1])
4242
end
4343

44+
#same test with exact flows
45+
flow = mincost_flow(g, spzeros(lg.nv(g)),capacity, w, ClpSolver(),edge_demand_exact=true, edge_demand=demand,source_nodes=[5],sink_nodes= [6])
46+
@test flow[5,1] == 1
47+
@test flow[5,2] == 1
48+
@test flow[3,6] == 1
49+
@test flow[4,6] == 1
50+
@test flow[1,4] == 1
51+
@test flow[2,4] == 0
52+
@test flow[2,3] == 1
53+
@test flow[1,3] == 0
54+
@test sum(diag(flow)) == 0
55+
56+
# flow conservation property
57+
for n1 = 1:4
58+
@test sum(flow[n1,:]) sum(flow[:,n1])
59+
end
60+
61+
#--- Same test without edge_demands & positive cost should result in null flow
62+
flow = mincost_flow(g, spzeros(lg.nv(g)),capacity, w, ClpSolver(),source_nodes=[5],sink_nodes= [6])
63+
@test flow[5,1] == 0
64+
@test flow[5,2] == 0
65+
@test flow[3,6] == 0
66+
@test flow[4,6] == 0
67+
@test flow[1,4] == 0
68+
@test flow[2,4] == 0
69+
@test flow[2,3] == 0
70+
@test flow[1,3] == 0
71+
@test sum(diag(flow)) == 0
72+
73+
# flow conservation property
74+
for n1 = 1:4
75+
@test sum(flow[n1,:]) sum(flow[:,n1])
76+
end
77+
78+
79+
#run sam test again with nodal demands
80+
node_demand=spzeros(6)
81+
node_demand[5]=2
82+
node_demand[6]=-2
83+
#--- Same test without edge_demands
84+
flow = mincost_flow(g, node_demand,capacity, w, ClpSolver())
85+
@test flow[5,1] == 1
86+
@test flow[5,2] == 1
87+
@test flow[3,6] == 1
88+
@test flow[4,6] == 1
89+
@test flow[1,4] == 1
90+
@test flow[2,4] == 0
91+
@test flow[2,3] == 1
92+
@test flow[1,3] == 0
93+
@test sum(diag(flow)) == 0
94+
95+
# flow conservation property
96+
for n1 = 1:4
97+
@test sum(flow[n1,:]) sum(flow[:,n1])
98+
end
99+
100+
44101
# no demand => null flow
45102
d2 = spzeros(6,6)
46-
flow = mincost_flow(g, capacity, d2, w, ClpSolver(), 5, 6)
103+
flow = mincost_flow(g, spzeros(lg.nv(g)),capacity, w, ClpSolver(), edge_demand=d2,source_nodes=[5],sink_nodes= [6])
47104
for idx in 1:6
48105
for jdx in 1:6
49106
@test flow[idx,jdx] 0.0
@@ -63,7 +120,7 @@ using SparseArrays: spzeros
63120
demand = spzeros(6,6)
64121
demand[1,2] = 1
65122
costs = ones(6,6)
66-
flow = mincost_flow(g, capacity, demand, costs, ClpSolver())
123+
flow = mincost_flow(g,spzeros(lg.nv(g)), capacity, costs, ClpSolver(), edge_demand=demand)
67124
active_flows = [(1,2), (2,5), (5,6),(6,1)]
68125
for s in 1:6
69126
for t in 1:6
@@ -80,7 +137,7 @@ using SparseArrays: spzeros
80137
end
81138
# higher short-circuit cost
82139
costs[2,5] = 10.
83-
flow = mincost_flow(g, capacity, demand, costs, ClpSolver())
140+
flow = mincost_flow(g,spzeros(lg.nv(g)), capacity, costs, ClpSolver(), edge_demand=demand)
84141
active_flows = [(1,2),(2,3),(3,4),(4,5),(5,6),(6,1)]
85142
for s in 1:6
86143
for t in 1:6

test/mincost_dep.jl

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Clp: ClpSolver
2+
using SparseArrays: spzeros
3+
#This is for testing of the depreciated interface. Please remove once that interface is removed
4+
@testset "Minimum-cost flow Depreciated" begin
5+
6+
# bipartite oriented + source & sink
7+
# source 5, sink 6, v1 {1, 2} v2 {3, 4}
8+
g = lg.DiGraph(6)
9+
lg.add_edge!(g, 5, 1)
10+
lg.add_edge!(g, 5, 2)
11+
lg.add_edge!(g, 3, 6)
12+
lg.add_edge!(g, 4, 6)
13+
lg.add_edge!(g, 1, 3)
14+
lg.add_edge!(g, 1, 4)
15+
lg.add_edge!(g, 2, 3)
16+
lg.add_edge!(g, 2, 4)
17+
w = zeros(6,6)
18+
w[1,3] = 10.
19+
w[1,4] = 5.
20+
w[2,3] = 2.
21+
w[2,4] = 2.
22+
# v2 -> sink have demand of one
23+
demand = spzeros(6,6)
24+
demand[3,6] = 1
25+
demand[4,6] = 1
26+
capacity = ones(6,6)
27+
28+
flow = mincost_flow(g, capacity, demand, w, ClpSolver(), 5, 6)
29+
@test flow[5,1] == 1
30+
@test flow[5,2] == 1
31+
@test flow[3,6] == 1
32+
@test flow[4,6] == 1
33+
@test flow[1,4] == 1
34+
@test flow[2,4] == 0
35+
@test flow[2,3] == 1
36+
@test flow[1,3] == 0
37+
@test sum(diag(flow)) == 0
38+
39+
# flow conservation property
40+
for n1 = 1:4
41+
@test sum(flow[n1,:]) sum(flow[:,n1])
42+
end
43+
44+
# no demand => null flow
45+
d2 = spzeros(6,6)
46+
flow = mincost_flow(g, capacity, d2, w, ClpSolver(), 5, 6)
47+
for idx in 1:6
48+
for jdx in 1:6
49+
@test flow[idx,jdx] 0.0
50+
end
51+
end
52+
53+
# graph without sink or source
54+
g = lg.DiGraph(6)
55+
# create circuit
56+
for s in 1:5
57+
lg.add_edge!(g, s, s+1)
58+
end
59+
lg.add_edge!(g, 6, 1)
60+
lg.add_edge!(g, 2, 5) # shortcut
61+
62+
capacity = ones(6,6)
63+
demand = spzeros(6,6)
64+
demand[1,2] = 1
65+
costs = ones(6,6)
66+
flow = mincost_flow(g, capacity, demand, costs, ClpSolver())
67+
active_flows = [(1,2), (2,5), (5,6),(6,1)]
68+
for s in 1:6
69+
for t in 1:6
70+
if (s,t) in active_flows
71+
@test flow[s,t] 1.
72+
else
73+
@test flow[s,t] 0.
74+
end
75+
end
76+
end
77+
# flow conservation property
78+
for n1 = 1:6
79+
@test sum(flow[n1,:]) sum(flow[:,n1])
80+
end
81+
# higher short-circuit cost
82+
costs[2,5] = 10.
83+
flow = mincost_flow(g, capacity, demand, costs, ClpSolver())
84+
active_flows = [(1,2),(2,3),(3,4),(4,5),(5,6),(6,1)]
85+
for s in 1:6
86+
for t in 1:6
87+
if (s,t) in active_flows
88+
@test flow[s,t] 1.
89+
else
90+
@test flow[s,t] 0.
91+
end
92+
end
93+
end
94+
# flow conservation property
95+
for n1 = 1:6
96+
@test sum(flow[n1,:]) sum(flow[:,n1])
97+
end
98+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ for t in [
1919
"maximum_flow",
2020
"multiroute_flow",
2121
"mincost",
22+
"mincost_dep", #Please remove this once depreciated mincost_flow iterface is removed
2223
"mincut",
2324
]
2425
tp = joinpath(testdir, "$(t).jl")

0 commit comments

Comments
 (0)