diff --git a/src/MultiObjectiveAlgorithms.jl b/src/MultiObjectiveAlgorithms.jl index 4447c85..b0d269e 100644 --- a/src/MultiObjectiveAlgorithms.jl +++ b/src/MultiObjectiveAlgorithms.jl @@ -579,6 +579,30 @@ function _compute_ideal_point(model::Optimizer, start_time) return end +function optimize_multiobjective!( + algorithm::AbstractAlgorithm, + model::Optimizer, +) + sense = MOI.get(model.inner, MOI.ObjectiveSense()) + if sense == MOI.FEASIBILITY_SENSE + return MOI.INVALID_MODEL, nothing + elseif sense == MOI.MAX_SENSE + old_obj = copy(model.f) + neg_obj = MOI.Utilities.operate(-, Float64, model.f) + MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + status, solutions = minimize_multiobjective!(algorithm, model) + MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + if solutions !== nothing + solutions = [SolutionPoint(s.x, -s.y) for s in solutions] + end + model.ideal_point *= -1 + return status, solutions + end + return minimize_multiobjective!(algorithm, model) +end + function MOI.optimize!(model::Optimizer) start_time = time() empty!(model.solutions) diff --git a/src/algorithms/Chalmet.jl b/src/algorithms/Chalmet.jl index 4eb44a7..5439b90 100644 --- a/src/algorithms/Chalmet.jl +++ b/src/algorithms/Chalmet.jl @@ -40,24 +40,12 @@ function _solve_constrained_model( return status, SolutionPoint(X, Y) end -function optimize_multiobjective!(algorithm::Chalmet, model::Optimizer) +function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE start_time = time() if MOI.output_dimension(model.f) != 2 error("Chalmet requires exactly two objectives") end - sense = MOI.get(model.inner, MOI.ObjectiveSense()) - if sense == MOI.MAX_SENSE - old_obj, neg_obj = copy(model.f), -model.f - MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - status, solutions = optimize_multiobjective!(algorithm, model) - MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - if solutions !== nothing - solutions = [SolutionPoint(s.x, -s.y) for s in solutions] - end - return status, solutions - end solutions = SolutionPoint[] E = Tuple{Int,Int}[] Q = Tuple{Int,Int}[] diff --git a/src/algorithms/DominguezRios.jl b/src/algorithms/DominguezRios.jl index 8fe8984..b88f842 100644 --- a/src/algorithms/DominguezRios.jl +++ b/src/algorithms/DominguezRios.jl @@ -142,22 +142,9 @@ function _update!( return end -function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) +function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE start_time = time() - sense = MOI.get(model.inner, MOI.ObjectiveSense()) - if sense == MOI.MAX_SENSE - old_obj, neg_obj = copy(model.f), -model.f - MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - status, solutions = optimize_multiobjective!(algorithm, model) - MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - if solutions !== nothing - solutions = [SolutionPoint(s.x, -s.y) for s in solutions] - end - model.ideal_point .*= -1 - return status, solutions - end n = MOI.output_dimension(model.f) L = [_DominguezRiosBox[] for i in 1:n] scalars = MOI.Utilities.scalarize(model.f) @@ -166,7 +153,7 @@ function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) # Ideal and Nadir point estimation for (i, f_i) in enumerate(scalars) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) - MOI.set(model.inner, MOI.ObjectiveSense(), sense) + MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.optimize!(model.inner) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) @@ -175,18 +162,17 @@ function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) _, Y = _compute_point(model, variables, f_i) yI[i] = Y model.ideal_point[i] = Y - rev_sense = sense == MOI.MIN_SENSE ? MOI.MAX_SENSE : MOI.MIN_SENSE - MOI.set(model.inner, MOI.ObjectiveSense(), rev_sense) + MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) MOI.optimize!(model.inner) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) - _warn_on_nonfinite_anti_ideal(algorithm, sense, i) + _warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i) return status, nothing end _, Y = _compute_point(model, variables, f_i) yN[i] = Y + 1 end - MOI.set(model.inner, MOI.ObjectiveSense(), sense) + MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) ϵ = 1 / (2 * n * (maximum(yN - yI) - 1)) # If ϵ is small, then the scalar objectives can contain terms that fall # below the tolerance level of the solver. To fix this, we rescale the diff --git a/src/algorithms/EpsilonConstraint.jl b/src/algorithms/EpsilonConstraint.jl index 58e7605..0f230f5 100644 --- a/src/algorithms/EpsilonConstraint.jl +++ b/src/algorithms/EpsilonConstraint.jl @@ -65,10 +65,11 @@ function MOI.get(alg::EpsilonConstraint, ::ObjectiveAbsoluteTolerance) return MOI.get(alg, EpsilonConstraintStep()) end -function optimize_multiobjective!( +function minimize_multiobjective!( algorithm::EpsilonConstraint, model::Optimizer, ) + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE start_time = time() if MOI.output_dimension(model.f) != 2 error("EpsilonConstraint requires exactly two objectives") @@ -87,12 +88,7 @@ function optimize_multiobjective!( end a, b = solution_1[1].y[1], solution_2[1].y[1] left, right = min(a, b), max(a, b) - sense = MOI.get(model.inner, MOI.ObjectiveSense()) - if sense == MOI.MIN_SENSE - model.ideal_point .= min.(solution_1[1].y, solution_2[1].y) - else - model.ideal_point .= max.(solution_1[1].y, solution_2[1].y) - end + model.ideal_point .= min.(solution_1[1].y, solution_2[1].y) # Compute the epsilon that we will be incrementing by each iteration ε = MOI.get(algorithm, EpsilonConstraintStep()) n_points = MOI.get(algorithm, SolutionLimit()) @@ -104,16 +100,12 @@ function optimize_multiobjective!( MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f2)}(), f2) # Add epsilon constraint variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) - SetType, bound = if sense == MOI.MIN_SENSE - MOI.LessThan{Float64}, right - ε - else - MOI.GreaterThan{Float64}, left + ε - end + bound = right - ε constant = MOI.constant(f1, Float64) ci = MOI.Utilities.normalize_and_add_constraint( model, f1, - SetType(bound); + MOI.LessThan{Float64}(bound); allow_modify_function = true, ) bound -= constant @@ -123,7 +115,7 @@ function optimize_multiobjective!( status = MOI.TIME_LIMIT break end - MOI.set(model, MOI.ConstraintSet(), ci, SetType(bound)) + MOI.set(model, MOI.ConstraintSet(), ci, MOI.LessThan{Float64}(bound)) MOI.optimize!(model.inner) if !_is_scalar_status_optimal(model) break @@ -132,12 +124,8 @@ function optimize_multiobjective!( if isempty(solutions) || !(Y ≈ solutions[end].y) push!(solutions, SolutionPoint(X, Y)) end - if sense == MOI.MIN_SENSE - bound = min(Y[1] - constant - ε, bound - ε) - else - bound = max(Y[1] - constant + ε, bound + ε) - end + bound = min(Y[1] - constant - ε, bound - ε) end MOI.delete(model, ci) - return status, filter_nondominated(sense, solutions) + return status, filter_nondominated(MOI.MIN_SENSE, solutions) end diff --git a/src/algorithms/Hierarchical.jl b/src/algorithms/Hierarchical.jl index cd0e882..f7783f1 100644 --- a/src/algorithms/Hierarchical.jl +++ b/src/algorithms/Hierarchical.jl @@ -85,7 +85,8 @@ function _sorted_priorities(priorities::Vector{Int}) return [findall(isequal(u), priorities) for u in unique_priorities] end -function optimize_multiobjective!(algorithm::Hierarchical, model::Optimizer) +function minimize_multiobjective!(algorithm::Hierarchical, model::Optimizer) + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE objectives = MOI.Utilities.eachscalar(model.f) N = length(objectives) variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) @@ -109,14 +110,9 @@ function optimize_multiobjective!(algorithm::Hierarchical, model::Optimizer) end # Add tolerance constraints X, Y = _compute_point(model, variables, new_vector_f) - sense = MOI.get(model.inner, MOI.ObjectiveSense()) for (i, fi) in enumerate(MOI.Utilities.eachscalar(new_vector_f)) rtol = MOI.get(algorithm, ObjectiveRelativeTolerance(i)) - set = if sense == MOI.MIN_SENSE - MOI.LessThan(Y[i] + rtol * abs(Y[i])) - else - MOI.GreaterThan(Y[i] - rtol * abs(Y[i])) - end + set = MOI.LessThan(Y[i] + rtol * abs(Y[i])) ci = MOI.Utilities.normalize_and_add_constraint(model, fi, set) push!(constraints, ci) end diff --git a/src/algorithms/KirlikSayin.jl b/src/algorithms/KirlikSayin.jl index 987b462..bcf7fe0 100644 --- a/src/algorithms/KirlikSayin.jl +++ b/src/algorithms/KirlikSayin.jl @@ -74,23 +74,9 @@ function _update_list(L::Vector{_Rectangle}, f::Vector{Float64}) return L_new end -function optimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) +function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE start_time = time() - sense = MOI.get(model.inner, MOI.ObjectiveSense()) - if sense == MOI.MAX_SENSE - old_obj, neg_obj = copy(model.f), -model.f - MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - status, solutions = optimize_multiobjective!(algorithm, model) - MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - if solutions !== nothing - solutions = [SolutionPoint(s.x, -s.y) for s in solutions] - end - model.ideal_point .*= -1 - return status, solutions - end - @assert sense == MOI.MIN_SENSE solutions = SolutionPoint[] # Problem with p objectives. # Set k = 1, meaning the nondominated points will get projected diff --git a/src/algorithms/TambyVanderpooten.jl b/src/algorithms/TambyVanderpooten.jl index b4b0a96..83fa1f0 100644 --- a/src/algorithms/TambyVanderpooten.jl +++ b/src/algorithms/TambyVanderpooten.jl @@ -81,25 +81,12 @@ function _select_search_zone( return k_star, upper_bounds[j_star] end -function optimize_multiobjective!( +function minimize_multiobjective!( algorithm::TambyVanderpooten, model::Optimizer, ) + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE start_time = time() - sense = MOI.get(model.inner, MOI.ObjectiveSense()) - if sense == MOI.MAX_SENSE - old_obj, neg_obj = copy(model.f), -model.f - MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - status, solutions = optimize_multiobjective!(algorithm, model) - MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - if solutions !== nothing - solutions = [SolutionPoint(s.x, -s.y) for s in solutions] - end - model.ideal_point .*= -1 - return status, solutions - end warm_start_supported = false if MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) warm_start_supported = true @@ -111,7 +98,7 @@ function optimize_multiobjective!( scalars = MOI.Utilities.scalarize(model.f) for (i, f_i) in enumerate(scalars) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) - MOI.set(model.inner, MOI.ObjectiveSense(), sense) + MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.optimize!(model.inner) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) @@ -124,7 +111,7 @@ function optimize_multiobjective!( MOI.optimize!(model.inner) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) - _warn_on_nonfinite_anti_ideal(algorithm, sense, i) + _warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i) return status, nothing end _, Y = _compute_point(model, variables, f_i) diff --git a/test/algorithms/Chalmet.jl b/test/algorithms/Chalmet.jl index 8ffefb7..6ddd404 100644 --- a/test/algorithms/Chalmet.jl +++ b/test/algorithms/Chalmet.jl @@ -165,6 +165,23 @@ function test_unbounded() return end +function test_invalid_feasibility() + model = MOA.Optimizer(HiGHS.Optimizer) + MOI.set(model, MOA.Algorithm(), MOA.Chalmet()) + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 2) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) + f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INVALID_MODEL + @test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION + @test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION + return +end + function test_infeasible() model = MOA.Optimizer(HiGHS.Optimizer) MOI.set(model, MOA.Algorithm(), MOA.Chalmet()) @@ -172,6 +189,7 @@ function test_infeasible() x = MOI.add_variables(model, 2) MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model) diff --git a/test/algorithms/DominguezRios.jl b/test/algorithms/DominguezRios.jl index fab67d1..8720bef 100644 --- a/test/algorithms/DominguezRios.jl +++ b/test/algorithms/DominguezRios.jl @@ -42,6 +42,7 @@ function test_infeasible() x = MOI.add_variables(model, 2) MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model) diff --git a/test/algorithms/EpsilonConstraint.jl b/test/algorithms/EpsilonConstraint.jl index d8f978e..0755659 100644 --- a/test/algorithms/EpsilonConstraint.jl +++ b/test/algorithms/EpsilonConstraint.jl @@ -245,6 +245,7 @@ function test_infeasible() x = MOI.add_variables(model, 2) MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model) diff --git a/test/algorithms/Hierarchical.jl b/test/algorithms/Hierarchical.jl index ab71063..ca7b59f 100644 --- a/test/algorithms/Hierarchical.jl +++ b/test/algorithms/Hierarchical.jl @@ -88,6 +88,7 @@ function test_infeasible() x = MOI.add_variables(model, 2) MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model) diff --git a/test/algorithms/TambyVanderpooten.jl b/test/algorithms/TambyVanderpooten.jl index 231fc4c..3226b7d 100644 --- a/test/algorithms/TambyVanderpooten.jl +++ b/test/algorithms/TambyVanderpooten.jl @@ -39,6 +39,7 @@ function test_infeasible() x = MOI.add_variables(model, 2) MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model)