Skip to content

Commit f793c76

Browse files
authored
Add a warning when lexicographic used with default args and 5+ objectives (#138)
1 parent 0be8f2f commit f793c76

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

src/MultiObjectiveAlgorithms.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ Controls whether to return the lexicographic solution for all permutations of
364364
the scalar objectives (when `true`), or only the solution corresponding to the
365365
lexicographic solution of the original objective function (when `false`).
366366
367-
Defaults to true`.
367+
Defaults to `true`.
368368
"""
369369
struct LexicographicAllPermutations <: AbstractAlgorithmAttribute end
370370

src/algorithms/Lexicographic.jl

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ point on the frontier, corresponding to solving each objective in order.
2525
"""
2626
mutable struct Lexicographic <: AbstractAlgorithm
2727
rtol::Vector{Float64}
28-
all_permutations::Bool
28+
all_permutations::Union{Nothing,Bool}
2929

3030
function Lexicographic(; all_permutations::Union{Nothing,Bool} = nothing)
3131
if all_permutations !== nothing
@@ -35,7 +35,7 @@ mutable struct Lexicographic <: AbstractAlgorithm
3535
"option to `$all_permutations` instead.",
3636
)
3737
end
38-
return new(Float64[], default(LexicographicAllPermutations()))
38+
return new(Float64[], nothing)
3939
end
4040
end
4141

@@ -67,9 +67,35 @@ end
6767
function optimize_multiobjective!(algorithm::Lexicographic, model::Optimizer)
6868
start_time = time()
6969
sequence = 1:MOI.output_dimension(model.f)
70-
if !MOI.get(algorithm, LexicographicAllPermutations())
70+
perm = MOI.get(algorithm, LexicographicAllPermutations())
71+
if !something(perm, default(LexicographicAllPermutations()))
7172
return _solve_in_sequence(algorithm, model, sequence, start_time)
7273
end
74+
if perm === nothing && length(sequence) >= 5
75+
o, n = length(sequence), factorial(length(sequence))
76+
@warn(
77+
"""
78+
The `MOA.Lexicographic` algorithm has been called with the default
79+
option for `MOA.LexicographicAllPermutations()`.
80+
81+
Because there are $o objectives, the algorithm will solve all
82+
$(o)! = $n permutations of the objectives.
83+
84+
To solve only a single sequence corresponding to the lexicographic
85+
order of the objective function, set `MOA.LexicographicAllPermutations()`
86+
to `false`:
87+
```julia
88+
set_attribute(model, MOA.LexicographicAllPermutations(), false)
89+
```
90+
91+
To disable this warning, explicitly opt-in to all permutations by
92+
setting it to `true`:
93+
```julia
94+
set_attribute(model, MOA.LexicographicAllPermutations(), true)
95+
```
96+
""",
97+
)
98+
end
7399
solutions = SolutionPoint[]
74100
status = MOI.OPTIMAL
75101
for sequence in Combinatorics.permutations(sequence)

test/algorithms/Lexicographic.jl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,49 @@ function test_knapsack_time_limit()
198198
return
199199
end
200200

201+
function test_knapsack_5_objectives()
202+
P = Float64[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1; 1 1 1 1]
203+
model = MOA.Optimizer(HiGHS.Optimizer)
204+
MOI.set(model, MOA.Algorithm(), MOA.Lexicographic())
205+
MOI.set(model, MOI.Silent(), true)
206+
x = MOI.add_variables(model, 4)
207+
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
208+
MOI.add_constraint.(model, x, MOI.LessThan(1.0))
209+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
210+
f = MOI.Utilities.operate(vcat, Float64, P * x...)
211+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
212+
MOI.add_constraint(model, sum(1.0 * x[i] for i in 1:4), MOI.LessThan(2.0))
213+
@test_logs (:warn,) MOI.optimize!(model)
214+
@test MOI.get(model, MOI.ResultCount()) == 6
215+
results = [
216+
[0, 0, 1, 1, 2] => [0, 0, 1, 1],
217+
[0, 1, 0, 1, 2] => [0, 1, 0, 1],
218+
[0, 1, 1, 0, 2] => [0, 1, 1, 0],
219+
[1, 0, 0, 1, 2] => [1, 0, 0, 1],
220+
[1, 0, 1, 0, 2] => [1, 0, 1, 0],
221+
[1, 1, 0, 0, 2] => [1, 1, 0, 0],
222+
]
223+
for i in 1:MOI.get(model, MOI.ResultCount())
224+
X = round.(Int, MOI.get(model, MOI.VariablePrimal(i), x))
225+
Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(i)))
226+
@test results[i] == (Y => X)
227+
end
228+
MOI.set(model, MOA.LexicographicAllPermutations(), true)
229+
@test_nowarn MOI.optimize!(model)
230+
for i in 1:MOI.get(model, MOI.ResultCount())
231+
X = round.(Int, MOI.get(model, MOI.VariablePrimal(i), x))
232+
Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(i)))
233+
@test results[i] == (Y => X)
234+
end
235+
MOI.set(model, MOA.LexicographicAllPermutations(), false)
236+
@test_nowarn MOI.optimize!(model)
237+
@test MOI.get(model, MOI.ResultCount()) == 1
238+
X = round.(Int, MOI.get(model, MOI.VariablePrimal(1), x))
239+
Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(1)))
240+
@test ([1, 1, 0, 0, 2] => [1, 1, 0, 0]) == (Y => X)
241+
return
242+
end
243+
201244
end # module TestLexicographic
202245

203246
TestLexicographic.run_tests()

0 commit comments

Comments
 (0)