diff --git a/Project.toml b/Project.toml index 98be7a4b..ac26f536 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Gurobi" uuid = "2e9cd046-0924-5485-92f1-d5272153d98b" repo = "https://github.com/JuliaOpt/Gurobi.jl" -version = "0.7.5" +version = "0.7.6" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/src/Gurobi.jl b/src/Gurobi.jl index 1efa7700..e6d3f42a 100644 --- a/src/Gurobi.jl +++ b/src/Gurobi.jl @@ -78,5 +78,6 @@ include("grb_callbacks.jl") include("MPB_wrapper.jl") include("MOI_wrapper.jl") include("MOI_callbacks.jl") +include("MOI_multi_objective.jl") end diff --git a/src/MOI_multi_objective.jl b/src/MOI_multi_objective.jl new file mode 100644 index 00000000..ddc5b5c6 --- /dev/null +++ b/src/MOI_multi_objective.jl @@ -0,0 +1,124 @@ +struct NumberOfObjectives <: MOI.AbstractModelAttribute end + +function MOI.set(model::Optimizer, ::NumberOfObjectives, n::Integer) + set_intattr!(model.inner, "NumObj", n) + return +end + +function MOI.get(model::Optimizer, ::NumberOfObjectives) + return get_intattr(model.inner, "NumObj") +end + +struct MultiObjectiveFunction <: MOI.AbstractModelAttribute + index::Int +end + +function c_api_setobjectiven( + model, + index::Cint, + priority::Cint, + weight::Cdouble, + abstol::Cdouble, + reltol::Cdouble, + name::String, + constant::Cdouble, + lnz::Cint, + lind::Vector{Cint}, + lval::Vector{Cdouble} +) + ret = @grb_ccall( + setobjectiven, + Cint, + ( + Ptr{Cvoid}, Cint, Cint, Cdouble, Cdouble, Cdouble, Ptr{Cchar}, + Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble} + ), + model, index, priority, weight, abstol, reltol, name, constant, lnz, lind, lval + ) + if ret != 0 + throw(GurobiError(model.env, ret)) + end +end + +function MOI.set( + model::Optimizer, + attr::MultiObjectiveFunction, + f::MOI.ScalarAffineFunction +) + num_vars = length(model.variable_info) + obj = zeros(Float64, num_vars) + for term in f.terms + column = _info(model, term.variable_index).column + obj[column] += term.coefficient + end + indices, coefficients = _indices_and_coefficients(model, f) + _update_if_necessary(model) + c_api_setobjectiven( + model.inner, + Cint(attr.index - 1), + Cint(0), + 1.0, + 0.0, + 0.0, + "", + f.constant, + Cint(length(indices)), + Cint.(indices) .- Cint(1), + coefficients + ) + _require_update(model) + return +end + +struct MultiObjectivePriority <: MOI.AbstractModelAttribute + index::Int +end + +function MOI.set( + model::Gurobi.Optimizer, attr::MultiObjectivePriority, priority::Int +) + set_int_param!(model.inner, "ObjNumber", attr.index - 1) + set_intattr!(model.inner, "ObjNPriority", priority) + _require_update(model) + return +end + +function MOI.get( + model::Gurobi.Optimizer, attr::MultiObjectivePriority +) + _update_if_necessary(model) + set_int_param!(model.inner, "ObjNumber", attr.index - 1) + return get_intattr(model.inner, "ObjNPriority") +end + +struct MultiObjectiveWeight <: MOI.AbstractModelAttribute + index::Int +end + +function MOI.set( + model::Gurobi.Optimizer, attr::MultiObjectiveWeight, weight::Float64 +) + set_int_param!(model.inner, "ObjNumber", attr.index - 1) + set_dblattr!(model.inner, "ObjNWeight", weight) + _require_update(model) + return +end + +function MOI.get( + model::Gurobi.Optimizer, attr::MultiObjectiveWeight +) + _update_if_necessary(model) + set_int_param!(model.inner, "ObjNumber", attr.index - 1) + return get_dblattr(model.inner, "ObjNWeight") +end + +struct MultiObjectiveValue <: MOI.AbstractModelAttribute + index::Int +end + +function MOI.get( + model::Gurobi.Optimizer, attr::MultiObjectiveValue +) + set_int_param!(model.inner, "ObjNumber", attr.index - 1) + return get_dblattr(model.inner, "ObjNVal") +end diff --git a/test/MOI_callbacks.jl b/test/MOI/MOI_callbacks.jl similarity index 100% rename from test/MOI_callbacks.jl rename to test/MOI/MOI_callbacks.jl diff --git a/test/MOI/MOI_multiobjective.jl b/test/MOI/MOI_multiobjective.jl new file mode 100644 index 00000000..00cb917d --- /dev/null +++ b/test/MOI/MOI_multiobjective.jl @@ -0,0 +1,75 @@ +using Gurobi +using Test + +const MOI = Gurobi.MOI + +@testset "MultiObjective Example" begin + model = Gurobi.Optimizer() + MOI.set(model, MOI.Silent(), true) + MOI.Utilities.loadfromstring!(model, """ + variables: x, y + minobjective: 2x + y + c1: x + y >= 1.0 + c2: 0.5 * x + 1.0 * y >= 0.75 + c3: x >= 0.0 + c4: y >= 0.25 + """) + x = MOI.get(model, MOI.VariableIndex, "x") + y = MOI.get(model, MOI.VariableIndex, "y") + + f = MOI.ScalarAffineFunction( + [MOI.ScalarAffineTerm(1.0, x), MOI.ScalarAffineTerm(3.0, y)], + 0.0 + ) + + MOI.set(model, Gurobi.MultiObjectiveFunction(2), f) + + @test MOI.get(model, Gurobi.MultiObjectiveWeight(1)) == 1.0 + @test MOI.get(model, Gurobi.MultiObjectiveWeight(2)) == 1.0 + @test MOI.get(model, Gurobi.MultiObjectivePriority(1)) == 0 + @test MOI.get(model, Gurobi.MultiObjectivePriority(2)) == 0 + + MOI.optimize!(model) + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1.5 + @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 0.5 + @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 0.5 + + BFS = [ + (x = 1.0, y = 0.25, f1 = 2.25, f2 = 1.75), + (x = 0.5, y = 0.5, f1 = 1.5, f2 = 2.0), + (x = 0.0, y = 1.0, f1 = 1.0, f2 = 3.0) + ] + for (i, λ) in enumerate([0.2, 0.5, 0.8]) + MOI.set(model, Gurobi.MultiObjectiveWeight(1), λ) + MOI.set(model, Gurobi.MultiObjectiveWeight(2), 1 - λ) + MOI.optimize!(model) + @test MOI.get(model, MOI.VariablePrimal(), x) ≈ BFS[i].x + @test MOI.get(model, MOI.VariablePrimal(), y) ≈ BFS[i].y + @test MOI.get(model, Gurobi.MultiObjectiveValue(1)) ≈ BFS[i].f1 + @test MOI.get(model, Gurobi.MultiObjectiveValue(2)) ≈ BFS[i].f2 + end + + MOI.set(model, Gurobi.MultiObjectiveWeight(1), 1.0) + MOI.set(model, Gurobi.MultiObjectiveWeight(2), 1.0) + MOI.set(model, Gurobi.MultiObjectivePriority(1), 1) + MOI.set(model, Gurobi.MultiObjectivePriority(2), 2) + @test MOI.get(model, Gurobi.MultiObjectivePriority(1)) == 1 + @test MOI.get(model, Gurobi.MultiObjectivePriority(2)) == 2 + + MOI.optimize!(model) + @test MOI.get(model, MOI.VariablePrimal(), x) ≈ BFS[1].x + @test MOI.get(model, MOI.VariablePrimal(), y) ≈ BFS[1].y + @test MOI.get(model, Gurobi.MultiObjectiveValue(1)) ≈ BFS[1].f1 + @test MOI.get(model, Gurobi.MultiObjectiveValue(2)) ≈ BFS[1].f2 + + MOI.set(model, Gurobi.MultiObjectivePriority(1), 2) + MOI.set(model, Gurobi.MultiObjectivePriority(2), 1) + @test MOI.get(model, Gurobi.MultiObjectivePriority(1)) == 2 + @test MOI.get(model, Gurobi.MultiObjectivePriority(2)) == 1 + + MOI.optimize!(model) + @test MOI.get(model, MOI.VariablePrimal(), x) ≈ BFS[3].x + @test MOI.get(model, MOI.VariablePrimal(), y) ≈ BFS[3].y + @test MOI.get(model, Gurobi.MultiObjectiveValue(1)) ≈ BFS[3].f1 + @test MOI.get(model, Gurobi.MultiObjectiveValue(2)) ≈ BFS[3].f2 +end diff --git a/test/MOI_wrapper.jl b/test/MOI/MOI_wrapper.jl similarity index 100% rename from test/MOI_wrapper.jl rename to test/MOI/MOI_wrapper.jl diff --git a/test/runtests.jl b/test/runtests.jl index 259e8f9c..4a209a0c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,6 +22,7 @@ end end @testset "MathOptInterface Tests" begin - include("MOI_wrapper.jl") - include("MOI_callbacks.jl") + @testset "$(file)" for file in readdir("MOI") + include(joinpath("MOI", file)) + end end