From abb07c179ea6f423f3b06a1c53caf86268c81775 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 9 Jun 2023 16:38:27 +1200 Subject: [PATCH 1/3] WIP: add support for VectorNonlinearFunction objectives --- Project.toml | 1 + src/MultiObjectiveAlgorithms.jl | 12 ++++++++++- src/algorithms/EpsilonConstraint.jl | 2 +- test/algorithms/EpsilonConstraint.jl | 32 ++++++++++++++++++++++++++++ test/runtests.jl | 3 +++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index fe06e5a..a24e6a1 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "1.1.0" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [compat] Combinatorics = "1" 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] diff --git a/test/runtests.jl b/test/runtests.jl index 73ca276..90c96a1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,9 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at http://mozilla.org/MPL/2.0/. +import Pkg +Pkg.pkg"add MathOptInterface#master" + using Test @testset "$file" for file in readdir(joinpath(@__DIR__, "algorithms")) From 97f651c836f2cb0b6aa87354800728a2982a46a3 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 16 Aug 2023 19:37:00 +1200 Subject: [PATCH 2/3] Update runtests.jl --- test/runtests.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 90c96a1..73ca276 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,9 +3,6 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at http://mozilla.org/MPL/2.0/. -import Pkg -Pkg.pkg"add MathOptInterface#master" - using Test @testset "$file" for file in readdir(joinpath(@__DIR__, "algorithms")) From a980d2f71cb95cd589ee5dc476b06201604fa95e Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 16 Aug 2023 19:37:23 +1200 Subject: [PATCH 3/3] Update Project.toml --- Project.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index a24e6a1..7f87176 100644 --- a/Project.toml +++ b/Project.toml @@ -6,13 +6,12 @@ version = "1.1.0" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [compat] Combinatorics = "1" HiGHS = "1" Ipopt = "1" -MathOptInterface = "1.15" +MathOptInterface = "1.19" julia = "1.6" [extras]