diff --git a/Project.toml b/Project.toml index e0e0d632..d5e882a1 100644 --- a/Project.toml +++ b/Project.toml @@ -9,7 +9,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" [compat] -MathOptInterface = "1.7" +MathOptInterface = "1.12" julia = "1.6" [extras] diff --git a/src/MOI_wrapper/MOI_multi_objective.jl b/src/MOI_wrapper/MOI_multi_objective.jl index 71a4b2db..c04da26a 100644 --- a/src/MOI_wrapper/MOI_multi_objective.jl +++ b/src/MOI_wrapper/MOI_multi_objective.jl @@ -126,3 +126,37 @@ 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 + model.objective_type = _VECTOR_AFFINE + 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}}, +) + return true +end diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 4eb8f224..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, @@ -1150,19 +1157,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 +1170,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}, @@ -3091,6 +3104,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) @@ -3409,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 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()