Skip to content

Commit 4225428

Browse files
authored
Add Chalmet algorithm (#41)
1 parent 7b0d975 commit 4225428

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Set the algorithm using the `MOA.Algorithm()` attribute.
4343

4444
The value must be one of the algorithms supported by MOA:
4545

46+
* `MOA.Chalmet()`
4647
* `MOA.EpsilonConstraint()`
4748
* `MOA.Dichotomy()`
4849
* `MOA.DominguezRios()`

src/algorithms/Chalmet.jl

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2019, Oscar Dowson and contributors
2+
# This Source Code Form is subject to the terms of the Mozilla Public License,
3+
# v.2.0. If a copy of the MPL was not distributed with this file, You can
4+
# obtain one at http://mozilla.org/MPL/2.0/.
5+
6+
"""
7+
Chalmet()
8+
9+
`Chalmet` implements the algorithm of:
10+
11+
Chalmet, L.G., and Lemonidis, L., and Elzinga, D.J. (1986). An algorithm for the
12+
bi-criterion integer programming problem. European Journal of Operational
13+
Research. 25(2), 292-300
14+
"""
15+
mutable struct Chalmet <: AbstractAlgorithm end
16+
17+
function _solve_constrained_model(
18+
model::Optimizer,
19+
::Chalmet,
20+
rhs::Vector{Float64},
21+
)
22+
f = MOI.Utilities.scalarize(model.f)
23+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(sum(f))}(), sum(f))
24+
constraints = [
25+
MOI.add_constraint(model.inner, f[1], MOI.LessThan(rhs[1] - 1))
26+
MOI.add_constraint(model.inner, f[2], MOI.LessThan(rhs[2] - 1))
27+
]
28+
MOI.optimize!(model.inner)
29+
MOI.delete.(model, constraints)
30+
status = MOI.get(model.inner, MOI.TerminationStatus())
31+
if !_is_scalar_status_optimal(status)
32+
return status, nothing
33+
end
34+
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
35+
X, Y = _compute_point(model, variables, model.f)
36+
return status, SolutionPoint(X, Y)
37+
end
38+
39+
function optimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
40+
if MOI.output_dimension(model.f) != 2
41+
error("Chalmet requires exactly two objectives")
42+
end
43+
sense = MOI.get(model.inner, MOI.ObjectiveSense())
44+
if sense == MOI.MAX_SENSE
45+
old_obj, neg_obj = copy(model.f), -model.f
46+
MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj)
47+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
48+
status, solutions = optimize_multiobjective!(algorithm, model)
49+
MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj)
50+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
51+
if solutions !== nothing
52+
solutions = [SolutionPoint(s.x, -s.y) for s in solutions]
53+
end
54+
return status, solutions
55+
end
56+
solutions = SolutionPoint[]
57+
E = Tuple{Int,Int}[]
58+
Q = Tuple{Int,Int}[]
59+
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
60+
f1, f2 = MOI.Utilities.scalarize(model.f)
61+
y1, y2 = zeros(2), zeros(2)
62+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f2)}(), f2)
63+
MOI.optimize!(model.inner)
64+
status = MOI.get(model.inner, MOI.TerminationStatus())
65+
if !_is_scalar_status_optimal(status)
66+
return status, nothing
67+
end
68+
_, y1[2] = _compute_point(model, variables, f2)
69+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1)
70+
y1_constraint = MOI.add_constraint(model.inner, f2, MOI.LessThan(y1[2]))
71+
MOI.optimize!(model.inner)
72+
x1, y1[1] = _compute_point(model, variables, f1)
73+
MOI.delete(model.inner, y1_constraint)
74+
push!(solutions, SolutionPoint(x1, y1))
75+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1)
76+
MOI.optimize!(model.inner)
77+
status = MOI.get(model.inner, MOI.TerminationStatus())
78+
if !_is_scalar_status_optimal(status)
79+
return status, nothing
80+
end
81+
_, y2[1] = _compute_point(model, variables, f1)
82+
if y2[1] solutions[1].y[1]
83+
return MOI.OPTIMAL, [solutions]
84+
end
85+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f2)}(), f2)
86+
y2_constraint = MOI.add_constraint(model.inner, f1, MOI.LessThan(y2[1]))
87+
MOI.optimize!(model.inner)
88+
x2, y2[2] = _compute_point(model, variables, f2)
89+
MOI.delete(model.inner, y2_constraint)
90+
push!(solutions, SolutionPoint(x2, y2))
91+
push!(Q, (1, 2))
92+
t = 3
93+
while !isempty(Q)
94+
r, s = pop!(Q)
95+
yr, ys = solutions[r].y, solutions[s].y
96+
rhs = [max(yr[1], ys[1]), max(yr[2], ys[2])]
97+
status, solution = _solve_constrained_model(model, algorithm, rhs)
98+
if !_is_scalar_status_optimal(status)
99+
push!(E, (r, s))
100+
continue
101+
end
102+
push!(solutions, solution)
103+
append!(Q, [(r, t), (t, s)])
104+
t += 1
105+
end
106+
return MOI.OPTIMAL, solutions
107+
end

test/algorithms/Chalmet.jl

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Copyright 2019, Oscar Dowson and contributors
2+
# This Source Code Form is subject to the terms of the Mozilla Public License,
3+
# v.2.0. If a copy of the MPL was not distributed with this file, You can
4+
# obtain one at http://mozilla.org/MPL/2.0/.
5+
6+
module TestChalmet
7+
8+
using Test
9+
10+
import HiGHS
11+
import MultiObjectiveAlgorithms as MOA
12+
13+
const MOI = MOA.MOI
14+
15+
function run_tests()
16+
for name in names(@__MODULE__; all = true)
17+
if startswith("$name", "test_")
18+
@testset "$name" begin
19+
getfield(@__MODULE__, name)()
20+
end
21+
end
22+
end
23+
return
24+
end
25+
26+
function test_knapsack_min()
27+
n = 10
28+
W = 2137.0
29+
C = Float64[
30+
566 611 506 180 817 184 585 423 26 317
31+
62 84 977 979 874 54 269 93 881 563
32+
]
33+
w = Float64[557, 898, 148, 63, 78, 964, 246, 662, 386, 272]
34+
model = MOA.Optimizer(HiGHS.Optimizer)
35+
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
36+
MOI.set(model, MOI.Silent(), true)
37+
x = MOI.add_variables(model, n)
38+
MOI.add_constraint.(model, x, MOI.ZeroOne())
39+
MOI.add_constraint(
40+
model,
41+
MOI.ScalarAffineFunction(
42+
[MOI.ScalarAffineTerm(w[j], x[j]) for j in 1:n],
43+
0.0,
44+
),
45+
MOI.LessThan(W),
46+
)
47+
f = MOI.VectorAffineFunction(
48+
[
49+
MOI.VectorAffineTerm(i, MOI.ScalarAffineTerm(-C[i, j], x[j]))
50+
for i in 1:2 for j in 1:n
51+
],
52+
[0.0, 0.0],
53+
)
54+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
55+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
56+
MOI.optimize!(model)
57+
X_E = Float64[
58+
0 0 1 1 1 0 1 1 1 1
59+
1 0 1 1 1 0 1 1 0 1
60+
0 1 1 1 1 0 1 0 1 1
61+
]
62+
Y_N = Float64[
63+
-2854 -4636
64+
-3394 -3817
65+
-3042 -4627
66+
]
67+
N = MOI.get(model, MOI.ResultCount())
68+
x_sol = hcat([MOI.get(model, MOI.VariablePrimal(i), x) for i in 1:N]...)
69+
@test isapprox(x_sol, X_E'; atol = 1e-6)
70+
y_sol = hcat([MOI.get(model, MOI.ObjectiveValue(i)) for i in 1:N]...)
71+
@test isapprox(y_sol, Y_N'; atol = 1e-6)
72+
return
73+
end
74+
75+
function test_knapsack_max()
76+
n = 10
77+
W = 2137.0
78+
C = Float64[
79+
566 611 506 180 817 184 585 423 26 317
80+
62 84 977 979 874 54 269 93 881 563
81+
]
82+
w = Float64[557, 898, 148, 63, 78, 964, 246, 662, 386, 272]
83+
model = MOA.Optimizer(HiGHS.Optimizer)
84+
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
85+
MOI.set(model, MOI.Silent(), true)
86+
x = MOI.add_variables(model, n)
87+
MOI.add_constraint.(model, x, MOI.ZeroOne())
88+
MOI.add_constraint(
89+
model,
90+
MOI.ScalarAffineFunction(
91+
[MOI.ScalarAffineTerm(w[j], x[j]) for j in 1:n],
92+
0.0,
93+
),
94+
MOI.LessThan(W),
95+
)
96+
f = MOI.VectorAffineFunction(
97+
[
98+
MOI.VectorAffineTerm(i, MOI.ScalarAffineTerm(C[i, j], x[j])) for
99+
i in 1:2 for j in 1:n
100+
],
101+
[0.0, 0.0],
102+
)
103+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
104+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
105+
MOI.optimize!(model)
106+
X_E = Float64[
107+
0 0 1 1 1 0 1 1 1 1
108+
1 0 1 1 1 0 1 1 0 1
109+
0 1 1 1 1 0 1 0 1 1
110+
]
111+
Y_N = Float64[
112+
2854 4636
113+
3394 3817
114+
3042 4627
115+
]
116+
N = MOI.get(model, MOI.ResultCount())
117+
x_sol = hcat([MOI.get(model, MOI.VariablePrimal(i), x) for i in 1:N]...)
118+
@test isapprox(x_sol, X_E'; atol = 1e-6)
119+
y_sol = hcat([MOI.get(model, MOI.ObjectiveValue(i)) for i in 1:N]...)
120+
@test isapprox(y_sol, Y_N'; atol = 1e-6)
121+
return
122+
end
123+
124+
function test_unbounded()
125+
model = MOA.Optimizer(HiGHS.Optimizer)
126+
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
127+
MOI.set(model, MOI.Silent(), true)
128+
x = MOI.add_variables(model, 2)
129+
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
130+
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
131+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
132+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
133+
MOI.optimize!(model)
134+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE
135+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION
136+
@test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
137+
return
138+
end
139+
140+
function test_infeasible()
141+
model = MOA.Optimizer(HiGHS.Optimizer)
142+
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
143+
MOI.set(model, MOI.Silent(), true)
144+
x = MOI.add_variables(model, 2)
145+
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
146+
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
147+
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
148+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
149+
MOI.optimize!(model)
150+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE
151+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION
152+
@test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
153+
return
154+
end
155+
156+
end
157+
158+
TestChalmet.run_tests()

0 commit comments

Comments
 (0)