From 5f5d2e50cb7b456a7eb516b5397042a33ec0fc8f Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 10 Jan 2023 15:49:31 +1300 Subject: [PATCH 1/4] WIP: begin support for vector-valued objective functions --- Project.toml | 1 + src/MOI_wrapper/MOI_multi_objective.jl | 18 ++++++++++++++ src/MOI_wrapper/MOI_wrapper.jl | 4 ++++ test/MOI/MOI_multiobjective.jl | 33 +++++++++++++++++++++++++- test/runtests.jl | 3 +++ 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e0e0d632..1fc776a4 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.11.5" LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [compat] MathOptInterface = "1.7" diff --git a/src/MOI_wrapper/MOI_multi_objective.jl b/src/MOI_wrapper/MOI_multi_objective.jl index 71a4b2db..1a36c40b 100644 --- a/src/MOI_wrapper/MOI_multi_objective.jl +++ b/src/MOI_wrapper/MOI_multi_objective.jl @@ -126,3 +126,21 @@ function MOI.get(model::Gurobi.Optimizer, attr::MultiObjectiveValue) _check_ret(model, ret) return val[] end + +function MOI.set( + model::Optimizer, + ::MOI.ObjectiveFunction{F}, + f::F, +) where {F<:MOI.VectorAffineFunction{Float64}} + for (i, fi) in enumerate(MOI.Utilities.eachscalar(f)) + MOI.set(model, MultiObjectiveFunction(i), fi) + end + return +end + +function MOI.supports( + model::Optimizer, + ::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}}, +) + return true +end diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 4eb8f224..06a660dc 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -3091,6 +3091,10 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue) attr.result_index - 1, ) end + N = MOI.get(model, NumberOfObjectives()) + if N > 1 + return [MOI.get(model, MultiObjectiveValue(i)) for i in 1:N] + end valueP = Ref{Cdouble}() key = attr.result_index == 1 ? "ObjVal" : "PoolObjVal" ret = GRBgetdblattr(model, key, valueP) diff --git a/test/MOI/MOI_multiobjective.jl b/test/MOI/MOI_multiobjective.jl index 0dbee466..e1531ca6 100644 --- a/test/MOI/MOI_multiobjective.jl +++ b/test/MOI/MOI_multiobjective.jl @@ -55,7 +55,7 @@ c4: y >= 0.25 @test MOI.get(model, Gurobi.MultiObjectivePriority(2)) == 0 MOI.optimize!(model) - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1.5 + @test MOI.get(model, MOI.ObjectiveValue()) ≈ [1.5, 2.0] @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 0.5 @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 0.5 @@ -101,6 +101,37 @@ c4: y >= 0.25 @test MOI.get(model, Gurobi.MultiObjectiveValue(2)) ≈ BFS[3].f2 end +function test_example_biobjective_knapsack() + p1 = [77.0, 94, 71, 63, 96, 82, 85, 75, 72, 91, 99, 63, 84, 87, 79, 94, 90] + p2 = [65.0, 90, 90, 77, 95, 84, 70, 94, 66, 92, 74, 97, 60, 60, 65, 97, 93] + w = [80.0, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91] + model = Gurobi.Optimizer() + x = MOI.add_variables(model, length(w)) + MOI.add_constraint.(model, x, MOI.ZeroOne()) + MOI.add_constraint(model, w' * x, MOI.LessThan(900.0)) + obj_f = MOI.Utilities.operate(vcat, Float64, p1' * x, p2' * x) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(obj_f)}(), obj_f) + MOI.optimize!(model) + results = Dict( + [955.0, 906.0] => [2, 3, 5, 6, 9, 10, 11, 14, 15, 16, 17], + [948.0, 939.0] => [1, 2, 3, 5, 6, 8, 10, 11, 15, 16, 17], + [934.0, 971.0] => [2, 3, 5, 6, 8, 10, 11, 12, 15, 16, 17], + [918.0, 983.0] => [2, 3, 4, 5, 6, 8, 10, 11, 12, 16, 17], + ) + found_non_dominated_point = false + for i in 1:MOI.get(model, MOI.ResultCount()) + X = findall(elt -> elt > 0.9, MOI.get.(model, MOI.VariablePrimal(i), x)) + Y = MOI.get(model, MOI.ObjectiveValue(i)) + if haskey(results, Y) + @test results[Y] == X + found_non_dominated_point = true + end + end + @test found_non_dominated_point + return +end + end TestMultiobjective.runtests() diff --git a/test/runtests.jl b/test/runtests.jl index d63b7cce..74beaa38 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,9 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. +import Pkg +Pkg.pkg"add MathOptInterface#od/vector-optimization" + using Gurobi using Test From 1eb96d3fb0c88099ef8309a9c57fe647390101b7 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 10 Jan 2023 16:26:29 +1300 Subject: [PATCH 2/4] Add support for getting objective --- src/MOI_wrapper/MOI_multi_objective.jl | 15 +++++++++++++ src/MOI_wrapper/MOI_wrapper.jl | 30 +++++++++++++++----------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/MOI_wrapper/MOI_multi_objective.jl b/src/MOI_wrapper/MOI_multi_objective.jl index 1a36c40b..e74fea8d 100644 --- a/src/MOI_wrapper/MOI_multi_objective.jl +++ b/src/MOI_wrapper/MOI_multi_objective.jl @@ -138,6 +138,21 @@ function MOI.set( return end +function MOI.get( + model::Optimizer, + ::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}}, +) + env = GRBgetenv(model) + F = MOI.ScalarAffineFunction{Float64} + f = F[] + for i in 1:MOI.get(model, NumberOfObjectives()) + ret = GRBsetintparam(env, "ObjNumber", i - 1) + _check_ret(env, ret) + push!(f, _get_affine_objective(model; is_multiobjective = true)) + end + return MOI.Utilities.operate(vcat, Float64, f...) +end + function MOI.supports( model::Optimizer, ::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}}, diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 06a660dc..264564a9 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -1150,19 +1150,11 @@ function MOI.set( return end -function MOI.get( - model::Optimizer, - ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, -) - if model.objective_type == _SCALAR_QUADRATIC - error( - "Unable to get objective function. Currently: " * - "$(model.objective_type).", - ) - end +function _get_affine_objective(model::Optimizer; is_multiobjective::Bool) _update_if_necessary(model) dest = zeros(length(model.variable_info)) - ret = GRBgetdblattrarray(model, "Obj", 0, length(dest), dest) + name = is_multiobjective ? "ObjN" : "Obj" + ret = GRBgetdblattrarray(model, name, 0, length(dest), dest) _check_ret(model, ret) terms = MOI.ScalarAffineTerm{Float64}[] for (index, info) in model.variable_info @@ -1171,11 +1163,25 @@ function MOI.get( push!(terms, MOI.ScalarAffineTerm(coefficient, index)) end constant = Ref{Cdouble}() - ret = GRBgetdblattr(model, "ObjCon", constant) + name = is_multiobjective ? "ObjNCon" : "ObjCon" + ret = GRBgetdblattr(model, name, constant) _check_ret(model, ret) return MOI.ScalarAffineFunction(terms, constant[]) end +function MOI.get( + model::Optimizer, + ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, +) + if model.objective_type == _SCALAR_QUADRATIC + error( + "Unable to get objective function. Currently: " * + "$(model.objective_type).", + ) + end + return _get_affine_objective(model; is_multiobjective = false) +end + function MOI.set( model::Optimizer, ::MOI.ObjectiveFunction{F}, From a498d54a958933134a11d5873a1d04dc9f9c678f Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 10 Jan 2023 16:45:37 +1300 Subject: [PATCH 3/4] Update --- src/MOI_wrapper/MOI_multi_objective.jl | 1 + src/MOI_wrapper/MOI_wrapper.jl | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/MOI_wrapper/MOI_multi_objective.jl b/src/MOI_wrapper/MOI_multi_objective.jl index e74fea8d..c04da26a 100644 --- a/src/MOI_wrapper/MOI_multi_objective.jl +++ b/src/MOI_wrapper/MOI_multi_objective.jl @@ -135,6 +135,7 @@ function MOI.set( for (i, fi) in enumerate(MOI.Utilities.eachscalar(f)) MOI.set(model, MultiObjectiveFunction(i), fi) end + model.objective_type = _VECTOR_AFFINE return end diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 264564a9..24f3efb1 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -18,7 +18,14 @@ const CleverDicts = MOI.Utilities.CleverDicts _INTERVAL, _EQUAL_TO ) -@enum(_ObjectiveType, _SINGLE_VARIABLE, _SCALAR_AFFINE, _SCALAR_QUADRATIC) +@enum( + _ObjectiveType, + _SINGLE_VARIABLE, + _SCALAR_AFFINE, + _SCALAR_QUADRATIC, + _VECTOR_AFFINE, +) + @enum( _CallbackState, _CB_NONE, @@ -3419,9 +3426,11 @@ function MOI.get(model::Optimizer, ::MOI.ObjectiveFunctionType) return MOI.VariableIndex elseif model.objective_type == _SCALAR_AFFINE return MOI.ScalarAffineFunction{Float64} - else - @assert model.objective_type == _SCALAR_QUADRATIC + elseif model.objective_type == _SCALAR_QUADRATIC return MOI.ScalarQuadraticFunction{Float64} + else + @assert model.objective_type == _VECTOR_AFFINE + return MOI.VectorAffineFunction{Float64} end end From 7b8636f526691ea6e01844d647e2a88a24c9c133 Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 11 Feb 2023 12:20:29 +1300 Subject: [PATCH 4/4] Update to MOI v1.12.0 --- Project.toml | 3 +-- test/runtests.jl | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index 1fc776a4..d5e882a1 100644 --- a/Project.toml +++ b/Project.toml @@ -7,10 +7,9 @@ version = "0.11.5" LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [compat] -MathOptInterface = "1.7" +MathOptInterface = "1.12" julia = "1.6" [extras] diff --git a/test/runtests.jl b/test/runtests.jl index 74beaa38..d63b7cce 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,9 +4,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -import Pkg -Pkg.pkg"add MathOptInterface#od/vector-optimization" - using Gurobi using Test