diff --git a/Project.toml b/Project.toml index fe06e5a..7f87176 100644 --- a/Project.toml +++ b/Project.toml @@ -11,7 +11,7 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Combinatorics = "1" HiGHS = "1" Ipopt = "1" -MathOptInterface = "1.15" +MathOptInterface = "1.19" julia = "1.6" [extras] diff --git a/src/MultiObjectiveAlgorithms.jl b/src/MultiObjectiveAlgorithms.jl index 7cfa5a7..6ddce1a 100644 --- a/src/MultiObjectiveAlgorithms.jl +++ b/src/MultiObjectiveAlgorithms.jl @@ -88,6 +88,13 @@ function _scalarise(f::MOI.VectorQuadraticFunction, w::Vector{Float64}) return MOI.ScalarQuadraticFunction(quad_terms, affine_terms, constant) end +function _scalarise(f::MOI.VectorNonlinearFunction, w::Vector{Float64}) + scalars = map(zip(w, f.rows)) do (wi, fi) + return MOI.ScalarNonlinearFunction(:*, Any[wi, fi]) + end + return MOI.ScalarNonlinearFunction(:+, scalars) +end + abstract type AbstractAlgorithm end MOI.Utilities.map_indices(::Function, x::AbstractAlgorithm) = x @@ -493,6 +500,9 @@ function MOI.get(model::Optimizer, attr::MOI.ListOfModelAttributesSet) end function MOI.delete(model::Optimizer, x::MOI.VariableIndex) + if model.f isa MOI.VectorNonlinearFunction + throw(MOI.DeleteNotAllowed(x)) + end MOI.delete(model.inner, x) if model.f !== nothing model.f = MOI.Utilities.remove_variable(model.f, x) @@ -582,7 +592,7 @@ function _compute_point( X = Dict{MOI.VariableIndex,Float64}( x => MOI.get(model.inner, MOI.VariablePrimal(), x) for x in variables ) - Y = MOI.Utilities.eval_variables(x -> X[x], f) + Y = MOI.Utilities.eval_variables(Base.Fix1(getindex, X), model, f) return X, Y end diff --git a/src/algorithms/EpsilonConstraint.jl b/src/algorithms/EpsilonConstraint.jl index aad156c..6f219a6 100644 --- a/src/algorithms/EpsilonConstraint.jl +++ b/src/algorithms/EpsilonConstraint.jl @@ -106,7 +106,7 @@ function optimize_multiobjective!( end ci = MOI.add_constraint(model, f1, SetType(bound)) status = MOI.OPTIMAL - while true + for _ in 1:n_points if _time_limit_exceeded(model, start_time) status = MOI.TIME_LIMIT break diff --git a/test/algorithms/EpsilonConstraint.jl b/test/algorithms/EpsilonConstraint.jl index d3dd8dd..088ee6e 100644 --- a/test/algorithms/EpsilonConstraint.jl +++ b/test/algorithms/EpsilonConstraint.jl @@ -362,6 +362,38 @@ function test_poor_numerics() return end +function test_vectornonlinearfunction() + μ = [0.006898463772627643, -0.02972609131603086] + Q = [0.030446 0.00393731; 0.00393731 0.00713285] + N = 2 + model = MOA.Optimizer(Ipopt.Optimizer) + MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint()) + MOI.set(model, MOA.SolutionLimit(), 10) + MOI.set(model, MOI.Silent(), true) + w = MOI.add_variables(model, N) + MOI.add_constraint.(model, w, MOI.GreaterThan(0.0)) + MOI.add_constraint.(model, w, MOI.LessThan(1.0)) + MOI.add_constraint(model, sum(1.0 * w[i] for i in 1:N), MOI.EqualTo(1.0)) + f = MOI.VectorNonlinearFunction([ + μ' * w, + MOI.ScalarNonlinearFunction( + :/, + Any[μ'*w, MOI.ScalarNonlinearFunction(:sqrt, Any[w'*Q*w])], + ), + ]) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + MOI.optimize!(model) + @test MOI.get(model, MOI.ResultCount()) >= 1 + for i in 1:MOI.get(model, MOI.ResultCount()) + w_sol = MOI.get(model, MOI.VariablePrimal(i), w) + y = MOI.get(model, MOI.ObjectiveValue(i)) + @test y ≈ [μ' * w_sol, (μ' * w_sol) / sqrt(w_sol' * Q * w_sol)] + end + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + return +end + function test_time_limit() p1 = [77, 94, 71, 63, 96, 82, 85, 75, 72, 91, 99, 63, 84, 87, 79, 94, 90] p2 = [65, 90, 90, 77, 95, 84, 70, 94, 66, 92, 74, 97, 60, 60, 65, 97, 93]