Skip to content

Commit e2089e0

Browse files
committed
support unbalanced assignment problems
1 parent d2d9c4d commit e2089e0

File tree

4 files changed

+244
-6
lines changed

4 files changed

+244
-6
lines changed

src/OperationsResearchModels.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@ using JuMP, HiGHS
1414
# solve(m::MstProblem)::MstResult
1515
solve() = nothing
1616

17+
# balance(m::TransportationProblem)::TransportationProblem
18+
# balance(a::AssignmentProblem)::AssignmentProblem
19+
balance() = nothing
20+
21+
# isbalanced(m::TransportationProblem)::Bool
22+
# isbalanced(a::AssignmentProblem)::Bool
23+
isbalanced() = nothing
24+
25+
1726
export solve
27+
export balance
28+
export isbalanced
29+
1830

1931
include("utility.jl")
2032
include("network.jl")
@@ -65,7 +77,7 @@ import .ShortestPath: ShortestPathResult, ShortestPathProblem
6577
import .Network: Connection, nodes
6678
import .MaximumFlow: MaximumFlowResult, MaximumFlowProblem
6779
import .MinimumCostFlow: MinimumCostFlowProblem, MinimumCostFlowResult
68-
import .Assignment: AssignmentProblem, AssignmentResult
80+
import .Assignment: AssignmentProblem, AssignmentResult, isbalanced, balance
6981
import .Game: game, GameResult, game_solver
7082
import .MinimumSpanningTree: hasloop, MstResult, MstProblem
7183
import .PMedian: pmedian, pmedian_with_distances, PMedianResult
@@ -81,7 +93,7 @@ import .Simplex: SimplexProblem, simplexiterations, createsimplexproblem, gaussj
8193
export TransportationProblem, TransportationResult, balance, isbalanced, northwestcorner, leastcost
8294
export Connection, ShortestPathResult, MaximumFlowResult, MinimumCostFlowResult, nodes
8395
export ShortestPathProblem, MaximumFlowProblem, MinimumCostFlowProblem
84-
export AssignmentProblem, AssignmentResult
96+
export AssignmentProblem, AssignmentResult, isbalanced, balance
8597
export game, GameResult, game_solver
8698
export hasloop, MstResult, MstProblem
8799
export pmedian, pmedian_with_distances, PMedianResult

src/assignment.jl

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ using JuMP, HiGHS
55

66
export AssignmentProblem
77
export AssignmentResult
8+
export isbalanced
9+
export balance
810

9-
import ..OperationsResearchModels: solve
11+
import ..OperationsResearchModels: solve, isbalanced, balance
1012

1113

1214
"""
@@ -26,6 +28,8 @@ end
2628

2729

2830

31+
32+
2933
"""
3034
AssignmentResult(problem, solution, cost)
3135
@@ -47,6 +51,56 @@ struct AssignmentResult
4751
end
4852

4953

54+
"""
55+
isbalanced(problem::AssignmentProblem)::Bool
56+
57+
# Description
58+
59+
Checks if the assignment problem is balanced, meaning the number of workers equals the number of tasks.
60+
61+
# Arguments
62+
63+
- `problem::AssignmentProblem`: The assignment problem to check.
64+
65+
# Returns
66+
67+
- `Bool`: Returns `true` if the problem is balanced, otherwise `false`.
68+
"""
69+
function isbalanced(problem::AssignmentProblem)::Bool
70+
n, p = size(problem.costs)
71+
return n == p
72+
end
73+
74+
75+
"""
76+
balance(problem::AssignmentProblem)::AssignmentProblem
77+
78+
# Description
79+
80+
Balances the assignment problem by adding dummy rows or columns if necessary.
81+
82+
# Arguments
83+
84+
- `problem::AssignmentProblem`: The assignment problem to balance.
85+
86+
# Returns
87+
88+
- `AssignmentProblem`: A new `AssignmentProblem` instance with balanced costs.
89+
"""
90+
function balance(problem::AssignmentProblem)::AssignmentProblem
91+
n, p = size(problem.costs)
92+
if n == p
93+
return problem
94+
elseif n < p
95+
new_costs = vcat(problem.costs, zeros(p-n, p))
96+
return AssignmentProblem(new_costs)
97+
else
98+
new_costs = hcat(problem.costs, zeros(n, n-p))
99+
return AssignmentProblem(new_costs)
100+
end
101+
end
102+
103+
50104

51105
"""
52106
solve(a)
@@ -90,10 +144,15 @@ function solve(a::AssignmentProblem)::AssignmentResult
90144
MOI.set(model, MOI.Silent(), true)
91145

92146
n, p = size(a.costs)
93-
@assert n == p
147+
problem = a
148+
149+
if n != p
150+
problem = balance(a)
151+
n, p = size(problem.costs)
152+
end
94153

95154
@variable(model, x[1:n, 1:p], Bin)
96-
@objective(model, Min, sum(a.costs .* x))
155+
@objective(model, Min, sum(problem.costs .* x))
97156

98157
@constraint(model, sum(x[1:n, j] for j = 1:p) .== 1.0)
99158
@constraint(model, sum(x[i, 1:p] for i = 1:n) .== 1.0)

src/transportation.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export leastcost
1111
export isbalanced
1212
export balance
1313

14-
import ..OperationsResearchModels: solve
14+
import ..OperationsResearchModels: solve, balance, isbalanced
1515

1616

1717
"""

test/testassignment.jl

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,98 @@
11
@testset "Assignment Problem" verbose = true begin
22

3+
@testset "isbalanced -> true" begin
4+
mat = [
5+
4 8 1
6+
3 1 9
7+
1 6 7
8+
]
9+
a = AssignmentProblem(mat)
10+
@test isbalanced(a) == true
11+
end
12+
13+
@testset "isbalanced -> false" begin
14+
mat = [
15+
4 8 1
16+
3 1 9
17+
]
18+
a = AssignmentProblem(mat)
19+
@test isbalanced(a) == false
20+
end
21+
22+
@testset "balance (2x3)" begin
23+
mat = [
24+
4 8 1
25+
3 1 9
26+
]
27+
a = AssignmentProblem(mat)
28+
balanced_a = balance(a)
29+
30+
@test balanced_a isa AssignmentProblem
31+
@test size(balanced_a.costs) == (3, 3)
32+
@test balanced_a.costs == [
33+
4 8 1
34+
3 1 9
35+
0 0 0
36+
]
37+
end
38+
39+
@testset "balance (2x4)" begin
40+
mat = [
41+
4 8 1 2
42+
3 1 9 5
43+
]
44+
a = AssignmentProblem(mat)
45+
balanced_a = balance(a)
46+
47+
@test balanced_a isa AssignmentProblem
48+
@test size(balanced_a.costs) == (4, 4)
49+
@test balanced_a.costs == [
50+
4 8 1 2
51+
3 1 9 5
52+
0 0 0 0
53+
0 0 0 0
54+
]
55+
end
56+
57+
@testset "balance (3x2)" begin
58+
mat = [
59+
4 8
60+
3 1
61+
1 6
62+
]
63+
a = AssignmentProblem(mat)
64+
balanced_a = balance(a)
65+
66+
@test balanced_a isa AssignmentProblem
67+
@test size(balanced_a.costs) == (3, 3)
68+
@test balanced_a.costs == [
69+
4 8 0
70+
3 1 0
71+
1 6 0
72+
]
73+
end
74+
75+
@testset "balance (4x2)" begin
76+
mat = [
77+
4 8
78+
3 1
79+
1 6
80+
2 5
81+
]
82+
a = AssignmentProblem(mat)
83+
balanced_a = balance(a)
84+
85+
@test balanced_a isa AssignmentProblem
86+
@test size(balanced_a.costs) == (4, 4)
87+
@test balanced_a.costs == [
88+
4 8 0 0
89+
3 1 0 0
90+
1 6 0 0
91+
2 5 0 0
92+
]
93+
end
94+
95+
396
@testset "Simple Assignment Problem" begin
497
mat = [
598
4 8 1
@@ -46,4 +139,78 @@
46139
end
47140

48141
end
142+
143+
144+
@testset "Solve Unbalanced Assignment Problem (2x3)" begin
145+
mat = [
146+
4 8 1
147+
3 1 9
148+
]
149+
a = AssignmentProblem(mat)
150+
result::AssignmentResult = solve(a)
151+
152+
@test result.problem isa AssignmentProblem
153+
@test result.cost == 2.0
154+
@test result.solution == [
155+
0 0 1
156+
0 1 0
157+
1 0 0
158+
]
159+
end
160+
161+
@testset "Solve Unbalanced Assignment Problem (2x4)" begin
162+
mat = [
163+
4 8 1 2
164+
3 1 9 5
165+
]
166+
a = AssignmentProblem(mat)
167+
result::AssignmentResult = solve(a)
168+
169+
@test result.problem isa AssignmentProblem
170+
@test result.cost == 2.0
171+
@test result.solution == [
172+
0 0 1 0
173+
0 1 0 0
174+
0 0 0 1
175+
1 0 0 0
176+
]
177+
end
178+
179+
@testset "Solve Unbalanced Assignment Problem (3x2)" begin
180+
mat = [
181+
4 8
182+
3 1
183+
1 6
184+
]
185+
a = AssignmentProblem(mat)
186+
result::AssignmentResult = solve(a)
187+
188+
@test result.problem isa AssignmentProblem
189+
@test result.cost == 2.0
190+
@test result.solution == [
191+
0 0 1
192+
0 1 0
193+
1 0 0
194+
]
195+
end
196+
197+
@testset "Solve Unbalanced Assignment Problem (4x2)" begin
198+
mat = [
199+
4 8
200+
3 1
201+
1 6
202+
2 5
203+
]
204+
a = AssignmentProblem(mat)
205+
result::AssignmentResult = solve(a)
206+
207+
@test result.problem isa AssignmentProblem
208+
@test result.cost == 2.0
209+
@test result.solution == [
210+
0 0 0 1
211+
0 1 0 0
212+
1 0 0 0
213+
0 0 1 0
214+
]
215+
end
49216
end

0 commit comments

Comments
 (0)