Skip to content

Commit e8423f8

Browse files
authored
Add support for vector-valued objective functions (#497)
1 parent 3ee3e37 commit e8423f8

File tree

4 files changed

+101
-17
lines changed

4 files changed

+101
-17
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
99
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
1010

1111
[compat]
12-
MathOptInterface = "1.7"
12+
MathOptInterface = "1.12"
1313
julia = "1.6"
1414

1515
[extras]

src/MOI_wrapper/MOI_multi_objective.jl

+34
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,37 @@ function MOI.get(model::Gurobi.Optimizer, attr::MultiObjectiveValue)
126126
_check_ret(model, ret)
127127
return val[]
128128
end
129+
130+
function MOI.set(
131+
model::Optimizer,
132+
::MOI.ObjectiveFunction{F},
133+
f::F,
134+
) where {F<:MOI.VectorAffineFunction{Float64}}
135+
for (i, fi) in enumerate(MOI.Utilities.eachscalar(f))
136+
MOI.set(model, MultiObjectiveFunction(i), fi)
137+
end
138+
model.objective_type = _VECTOR_AFFINE
139+
return
140+
end
141+
142+
function MOI.get(
143+
model::Optimizer,
144+
::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}},
145+
)
146+
env = GRBgetenv(model)
147+
F = MOI.ScalarAffineFunction{Float64}
148+
f = F[]
149+
for i in 1:MOI.get(model, NumberOfObjectives())
150+
ret = GRBsetintparam(env, "ObjNumber", i - 1)
151+
_check_ret(env, ret)
152+
push!(f, _get_affine_objective(model; is_multiobjective = true))
153+
end
154+
return MOI.Utilities.operate(vcat, Float64, f...)
155+
end
156+
157+
function MOI.supports(
158+
model::Optimizer,
159+
::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}},
160+
)
161+
return true
162+
end

src/MOI_wrapper/MOI_wrapper.jl

+34-15
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ const CleverDicts = MOI.Utilities.CleverDicts
1818
_INTERVAL,
1919
_EQUAL_TO
2020
)
21-
@enum(_ObjectiveType, _SINGLE_VARIABLE, _SCALAR_AFFINE, _SCALAR_QUADRATIC)
21+
@enum(
22+
_ObjectiveType,
23+
_SINGLE_VARIABLE,
24+
_SCALAR_AFFINE,
25+
_SCALAR_QUADRATIC,
26+
_VECTOR_AFFINE,
27+
)
28+
2229
@enum(
2330
_CallbackState,
2431
_CB_NONE,
@@ -1150,19 +1157,11 @@ function MOI.set(
11501157
return
11511158
end
11521159

1153-
function MOI.get(
1154-
model::Optimizer,
1155-
::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
1156-
)
1157-
if model.objective_type == _SCALAR_QUADRATIC
1158-
error(
1159-
"Unable to get objective function. Currently: " *
1160-
"$(model.objective_type).",
1161-
)
1162-
end
1160+
function _get_affine_objective(model::Optimizer; is_multiobjective::Bool)
11631161
_update_if_necessary(model)
11641162
dest = zeros(length(model.variable_info))
1165-
ret = GRBgetdblattrarray(model, "Obj", 0, length(dest), dest)
1163+
name = is_multiobjective ? "ObjN" : "Obj"
1164+
ret = GRBgetdblattrarray(model, name, 0, length(dest), dest)
11661165
_check_ret(model, ret)
11671166
terms = MOI.ScalarAffineTerm{Float64}[]
11681167
for (index, info) in model.variable_info
@@ -1171,11 +1170,25 @@ function MOI.get(
11711170
push!(terms, MOI.ScalarAffineTerm(coefficient, index))
11721171
end
11731172
constant = Ref{Cdouble}()
1174-
ret = GRBgetdblattr(model, "ObjCon", constant)
1173+
name = is_multiobjective ? "ObjNCon" : "ObjCon"
1174+
ret = GRBgetdblattr(model, name, constant)
11751175
_check_ret(model, ret)
11761176
return MOI.ScalarAffineFunction(terms, constant[])
11771177
end
11781178

1179+
function MOI.get(
1180+
model::Optimizer,
1181+
::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
1182+
)
1183+
if model.objective_type == _SCALAR_QUADRATIC
1184+
error(
1185+
"Unable to get objective function. Currently: " *
1186+
"$(model.objective_type).",
1187+
)
1188+
end
1189+
return _get_affine_objective(model; is_multiobjective = false)
1190+
end
1191+
11791192
function MOI.set(
11801193
model::Optimizer,
11811194
::MOI.ObjectiveFunction{F},
@@ -3091,6 +3104,10 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue)
30913104
attr.result_index - 1,
30923105
)
30933106
end
3107+
N = MOI.get(model, NumberOfObjectives())
3108+
if N > 1
3109+
return [MOI.get(model, MultiObjectiveValue(i)) for i in 1:N]
3110+
end
30943111
valueP = Ref{Cdouble}()
30953112
key = attr.result_index == 1 ? "ObjVal" : "PoolObjVal"
30963113
ret = GRBgetdblattr(model, key, valueP)
@@ -3409,9 +3426,11 @@ function MOI.get(model::Optimizer, ::MOI.ObjectiveFunctionType)
34093426
return MOI.VariableIndex
34103427
elseif model.objective_type == _SCALAR_AFFINE
34113428
return MOI.ScalarAffineFunction{Float64}
3412-
else
3413-
@assert model.objective_type == _SCALAR_QUADRATIC
3429+
elseif model.objective_type == _SCALAR_QUADRATIC
34143430
return MOI.ScalarQuadraticFunction{Float64}
3431+
else
3432+
@assert model.objective_type == _VECTOR_AFFINE
3433+
return MOI.VectorAffineFunction{Float64}
34153434
end
34163435
end
34173436

test/MOI/MOI_multiobjective.jl

+32-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ c4: y >= 0.25
5555
@test MOI.get(model, Gurobi.MultiObjectivePriority(2)) == 0
5656

5757
MOI.optimize!(model)
58-
@test MOI.get(model, MOI.ObjectiveValue()) 1.5
58+
@test MOI.get(model, MOI.ObjectiveValue()) [1.5, 2.0]
5959
@test MOI.get(model, MOI.VariablePrimal(), x) 0.5
6060
@test MOI.get(model, MOI.VariablePrimal(), y) 0.5
6161

@@ -101,6 +101,37 @@ c4: y >= 0.25
101101
@test MOI.get(model, Gurobi.MultiObjectiveValue(2)) BFS[3].f2
102102
end
103103

104+
function test_example_biobjective_knapsack()
105+
p1 = [77.0, 94, 71, 63, 96, 82, 85, 75, 72, 91, 99, 63, 84, 87, 79, 94, 90]
106+
p2 = [65.0, 90, 90, 77, 95, 84, 70, 94, 66, 92, 74, 97, 60, 60, 65, 97, 93]
107+
w = [80.0, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91]
108+
model = Gurobi.Optimizer()
109+
x = MOI.add_variables(model, length(w))
110+
MOI.add_constraint.(model, x, MOI.ZeroOne())
111+
MOI.add_constraint(model, w' * x, MOI.LessThan(900.0))
112+
obj_f = MOI.Utilities.operate(vcat, Float64, p1' * x, p2' * x)
113+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
114+
MOI.set(model, MOI.ObjectiveFunction{typeof(obj_f)}(), obj_f)
115+
MOI.optimize!(model)
116+
results = Dict(
117+
[955.0, 906.0] => [2, 3, 5, 6, 9, 10, 11, 14, 15, 16, 17],
118+
[948.0, 939.0] => [1, 2, 3, 5, 6, 8, 10, 11, 15, 16, 17],
119+
[934.0, 971.0] => [2, 3, 5, 6, 8, 10, 11, 12, 15, 16, 17],
120+
[918.0, 983.0] => [2, 3, 4, 5, 6, 8, 10, 11, 12, 16, 17],
121+
)
122+
found_non_dominated_point = false
123+
for i in 1:MOI.get(model, MOI.ResultCount())
124+
X = findall(elt -> elt > 0.9, MOI.get.(model, MOI.VariablePrimal(i), x))
125+
Y = MOI.get(model, MOI.ObjectiveValue(i))
126+
if haskey(results, Y)
127+
@test results[Y] == X
128+
found_non_dominated_point = true
129+
end
130+
end
131+
@test found_non_dominated_point
132+
return
133+
end
134+
104135
end
105136

106137
TestMultiobjective.runtests()

0 commit comments

Comments
 (0)