From 49e403d2e278f8217336185ad59595512efdba88 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 14 Aug 2025 11:56:25 +1200 Subject: [PATCH 1/2] Add a warning when lexicographic used with default args and 5+ objectives --- src/MultiObjectiveAlgorithms.jl | 2 +- src/algorithms/Lexicographic.jl | 32 +++++++++++++++++++-- test/algorithms/Lexicographic.jl | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/MultiObjectiveAlgorithms.jl b/src/MultiObjectiveAlgorithms.jl index 0bc59b1..7a178ae 100644 --- a/src/MultiObjectiveAlgorithms.jl +++ b/src/MultiObjectiveAlgorithms.jl @@ -364,7 +364,7 @@ Controls whether to return the lexicographic solution for all permutations of the scalar objectives (when `true`), or only the solution corresponding to the lexicographic solution of the original objective function (when `false`). -Defaults to true`. +Defaults to `true`. """ struct LexicographicAllPermutations <: AbstractAlgorithmAttribute end diff --git a/src/algorithms/Lexicographic.jl b/src/algorithms/Lexicographic.jl index 52cd974..5f105a9 100644 --- a/src/algorithms/Lexicographic.jl +++ b/src/algorithms/Lexicographic.jl @@ -25,7 +25,7 @@ point on the frontier, corresponding to solving each objective in order. """ mutable struct Lexicographic <: AbstractAlgorithm rtol::Vector{Float64} - all_permutations::Bool + all_permutations::Union{Nothing,Bool} function Lexicographic(; all_permutations::Union{Nothing,Bool} = nothing) if all_permutations !== nothing @@ -35,7 +35,7 @@ mutable struct Lexicographic <: AbstractAlgorithm "option to `$all_permutations` instead.", ) end - return new(Float64[], default(LexicographicAllPermutations())) + return new(Float64[], nothing) end end @@ -67,9 +67,35 @@ end function optimize_multiobjective!(algorithm::Lexicographic, model::Optimizer) start_time = time() sequence = 1:MOI.output_dimension(model.f) - if !MOI.get(algorithm, LexicographicAllPermutations()) + perm = MOI.get(algorithm, LexicographicAllPermutations()) + if !something(perm, default(LexicographicAllPermutations())) return _solve_in_sequence(algorithm, model, sequence, start_time) end + if perm === nothing && length(sequence) >= 5 + o, n = length(sequence), factorial(length(sequence)) + @warn( + """ + The `MOA.Lexicographic` algorithm has been called with the default + option for `MOA.LexicographicAllPermutations()`. + + Because there are $o objectives, the algorithm will solve all + $(o)! = $n permutations of the objectives. + + To solve only a single sequence corresponding to the lexicographic + order of the objective function, set `MOA.LexicographicAllPermutations()` + to `false`: + ```julia + set_attribute(model, MOA.LexicographicAllPermutations(), false) + ``` + + To disable this warning, explicitly opt-in to all permutations by + setting it to `true`: + ```julia + set_attribute(model, MOA.LexicographicAllPermutations(), true) + ``` + """, + ) + end solutions = SolutionPoint[] status = MOI.OPTIMAL for sequence in Combinatorics.permutations(sequence) diff --git a/test/algorithms/Lexicographic.jl b/test/algorithms/Lexicographic.jl index 74fa36c..370b084 100644 --- a/test/algorithms/Lexicographic.jl +++ b/test/algorithms/Lexicographic.jl @@ -198,6 +198,55 @@ function test_knapsack_time_limit() return end +function test_knapsack_5_objectives() + P = Float64[ + 1 0 0 0; + 0 1 0 0; + 0 0 1 0; + 0 0 0 1; + 1 1 1 1; + ] + model = MOA.Optimizer(HiGHS.Optimizer) + MOI.set(model, MOA.Algorithm(), MOA.Lexicographic()) + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 4) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint.(model, x, MOI.LessThan(1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = MOI.Utilities.operate(vcat, Float64, P * x...) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + MOI.add_constraint(model, sum(1.0 * x[i] for i in 1:4), MOI.LessThan(2.0)) + @test_logs (:warn,) MOI.optimize!(model) + @test MOI.get(model, MOI.ResultCount()) == 6 + results = [ + [0, 0, 1, 1, 2] => [0, 0, 1, 1], + [0, 1, 0, 1, 2] => [0, 1, 0, 1], + [0, 1, 1, 0, 2] => [0, 1, 1, 0], + [1, 0, 0, 1, 2] => [1, 0, 0, 1], + [1, 0, 1, 0, 2] => [1, 0, 1, 0], + [1, 1, 0, 0, 2] => [1, 1, 0, 0], + ] + for i in 1:MOI.get(model, MOI.ResultCount()) + X = round.(Int, MOI.get(model, MOI.VariablePrimal(i), x)) + Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(i))) + @test results[i] == (Y => X) + end + MOI.set(model, MOA.LexicographicAllPermutations(), true) + @test_nowarn MOI.optimize!(model) + for i in 1:MOI.get(model, MOI.ResultCount()) + X = round.(Int, MOI.get(model, MOI.VariablePrimal(i), x)) + Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(i))) + @test results[i] == (Y => X) + end + MOI.set(model, MOA.LexicographicAllPermutations(), false) + @test_nowarn MOI.optimize!(model) + @test MOI.get(model, MOI.ResultCount()) == 1 + X = round.(Int, MOI.get(model, MOI.VariablePrimal(1), x)) + Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(1))) + @test ([1, 1, 0, 0, 2] => [1, 1, 0, 0]) == (Y => X) + return +end + end # module TestLexicographic TestLexicographic.run_tests() From 980debfcb260bc1bba176c00e0d1d8fbf4d2a310 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 14 Aug 2025 12:11:51 +1200 Subject: [PATCH 2/2] Fix format --- test/algorithms/Lexicographic.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/algorithms/Lexicographic.jl b/test/algorithms/Lexicographic.jl index 370b084..2238995 100644 --- a/test/algorithms/Lexicographic.jl +++ b/test/algorithms/Lexicographic.jl @@ -199,13 +199,7 @@ function test_knapsack_time_limit() end function test_knapsack_5_objectives() - P = Float64[ - 1 0 0 0; - 0 1 0 0; - 0 0 1 0; - 0 0 0 1; - 1 1 1 1; - ] + P = Float64[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1; 1 1 1 1] model = MOA.Optimizer(HiGHS.Optimizer) MOI.set(model, MOA.Algorithm(), MOA.Lexicographic()) MOI.set(model, MOI.Silent(), true)