Content-Length: 820647 | pFad | http://github.com/jump-dev/CPLEX.jl/commit/1f3359bc1deec7b291116ebdd70ad358ccbcde1e

C8 Add support for vector-valued objective functions (#419) · jump-dev/CPLEX.jl@1f3359b · GitHub
Skip to content

Commit 1f3359b

Browse files
authored
Add support for vector-valued objective functions (#419)
1 parent 8c7b756 commit 1f3359b

File tree

3 files changed

+239
-4
lines changed

3 files changed

+239
-4
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1111

1212
[compat]
1313
CEnum = "0.3, 0.4"
14-
MathOptInterface = "1.7"
14+
MathOptInterface = "1.12"
1515
julia = "1.6"
1616

1717
[extras]

src/MOI/MOI_wrapper.jl

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const CleverDicts = MOI.Utilities.CleverDicts
2424
_SCALAR_AFFINE,
2525
_SCALAR_QUADRATIC,
2626
_UNSET_OBJECTIVE,
27+
_VECTOR_AFFINE,
2728
)
2829

2930
@enum(
@@ -389,6 +390,7 @@ function MOI.supports(
389390
MOI.VariableIndex,
390391
MOI.ScalarAffineFunction{Float64},
391392
MOI.ScalarQuadraticFunction{Float64},
393+
MOI.VectorAffineFunction{Float64},
392394
},
393395
}
394396
return true
@@ -1102,6 +1104,87 @@ function MOI.get(
11021104
)
11031105
end
11041106

1107+
function MOI.set(
1108+
model::Optimizer,
1109+
::MOI.ObjectiveFunction{F},
1110+
f::F,
1111+
) where {F<:MOI.VectorAffineFunction{Float64}}
1112+
if model.objective_type == _SCALAR_QUADRATIC
1113+
# We need to zero out the existing quadratic objective.
1114+
num_vars = length(model.variable_info)
1115+
ret = CPXcopyquad(
1116+
model.env,
1117+
model.lp,
1118+
fill(Cint(0), num_vars),
1119+
fill(Cint(0), num_vars),
1120+
Ref{Cint}(),
1121+
Ref{Cdouble}(),
1122+
)
1123+
_check_ret(model, ret)
1124+
end
1125+
ret = CPXsetnumobjs(model.env, model.lp, Cint(MOI.output_dimension(f)))
1126+
_check_ret(model, ret)
1127+
for (i, fi) in enumerate(MOI.Utilities.eachscalar(f))
1128+
ret = CPXmultiobjsetobj(
1129+
model.env,
1130+
model.lp,
1131+
Cint(i - 1),
1132+
length(fi.terms),
1133+
[Cint(column(model, term.variable) - 1) for term in fi.terms],
1134+
[term.coefficient for term in fi.terms],
1135+
fi.constant,
1136+
1.0,
1137+
1,
1138+
CPX_NO_ABSTOL_CHANGE,
1139+
CPX_NO_RELTOL_CHANGE,
1140+
"Objective $i",
1141+
)
1142+
_check_ret(model, ret)
1143+
end
1144+
model.objective_type = _VECTOR_AFFINE
1145+
return
1146+
end
1147+
1148+
function MOI.get(
1149+
model::Optimizer,
1150+
::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}},
1151+
)
1152+
if model.objective_type != _VECTOR_AFFINE
1153+
error(
1154+
"Unable to get objective function. Currently: $(model.objective_type).",
1155+
)
1156+
end
1157+
n = CPXgetnumobjs(model.env, model.lp)
1158+
f = MOI.ScalarAffineFunction{Float64}[]
1159+
dest = zeros(Cdouble, length(model.variable_info))
1160+
for i in 1:n
1161+
offset_p = Ref{Cdouble}(0.0)
1162+
ret = CPXmultiobjgetobj(
1163+
model.env,
1164+
model.lp,
1165+
Cint(i - 1),
1166+
dest,
1167+
Cint(0),
1168+
Cint(length(dest) - 1),
1169+
offset_p,
1170+
C_NULL,
1171+
C_NULL,
1172+
C_NULL,
1173+
C_NULL,
1174+
)
1175+
_check_ret(model, ret)
1176+
terms = MOI.ScalarAffineTerm{Float64}[]
1177+
for (index, info) in model.variable_info
1178+
coefficient = dest[info.column]
1179+
if !iszero(coefficient)
1180+
push!(terms, MOI.ScalarAffineTerm(coefficient, index))
1181+
end
1182+
end
1183+
push!(f, MOI.ScalarAffineFunction(terms, offset_p[]))
1184+
end
1185+
return MOI.Utilities.operate(vcat, Float64, f...)
1186+
end
1187+
11051188
function MOI.modify(
11061189
model::Optimizer,
11071190
::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
@@ -2607,7 +2690,9 @@ function _optimize!(model)
26072690
prob_type = CPXgetprobtype(model.env, model.lp)
26082691
# There are prob_types other than the ones listed here, but the
26092692
# CPLEX.Optimizer should never encounter them.
2610-
ret = if prob_type in (CPXPROB_MILP, CPXPROB_MIQP, CPXPROB_MIQCP)
2693+
ret = if model.objective_type == _VECTOR_AFFINE
2694+
CPXmultiobjopt(model.env, model.lp, C_NULL)
2695+
elseif prob_type in (CPXPROB_MILP, CPXPROB_MIQP, CPXPROB_MIQCP)
26112696
CPXmipopt(model.env, model.lp)
26122697
elseif prob_type in (CPXPROB_QP, CPXPROB_QCP)
26132698
CPXqpopt(model.env, model.lp)
@@ -3169,6 +3254,16 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue)
31693254
MOI.check_result_index_bounds(model, attr)
31703255
if model.has_primal_certificate
31713256
return MOI.Utilities.get_fallback(model, attr)
3257+
elseif model.objective_type == _VECTOR_AFFINE
3258+
n = CPXgetnumobjs(model.env, model.lp)
3259+
objval = zeros(n)
3260+
objP = Ref{Cdouble}(0.0)
3261+
for i in 1:n
3262+
ret = CPXmultiobjgetobjval(model.env, model.lp, Cint(i - 1), objP)
3263+
_check_ret(model, ret)
3264+
objval[i] = objP[]
3265+
end
3266+
return objval
31723267
end
31733268
p = Ref{Cdouble}()
31743269
ret = CPXgetobjval(model.env, model.lp, p)
@@ -3491,9 +3586,11 @@ function MOI.get(model::Optimizer, ::MOI.ObjectiveFunctionType)
34913586
return MOI.VariableIndex
34923587
elseif model.objective_type == _SCALAR_AFFINE
34933588
return MOI.ScalarAffineFunction{Float64}
3494-
else
3495-
@assert model.objective_type == _SCALAR_QUADRATIC
3589+
elseif model.objective_type == _SCALAR_QUADRATIC
34963590
return MOI.ScalarQuadraticFunction{Float64}
3591+
else
3592+
@assert model.objective_type == _VECTOR_AFFINE
3593+
return MOI.VectorAffineFunction{Float64}
34973594
end
34983595
end
34993596

@@ -3933,3 +4030,53 @@ function MOI.get(
39334030
z = _dual_multiplier(model)
39344031
return [z * slack[_info(model, v).column] for v in f.variables]
39354032
end
4033+
4034+
"""
4035+
MultiObjectiveAttribute(index::Int, field::String)
4036+
4037+
An attribute to interact with CPLEX's multi-objective attributes.
4038+
4039+
`index` is the 1-based index of the scalar objective function to modify.
4040+
4041+
`field` is a `String` that must be one of:
4042+
4043+
* `"weight"`: to change the scalarization weight of the objective
4044+
* `"priority"`: to change the priority of the objective
4045+
* `"abstol"`: to change the absolute tolerance of the objective
4046+
* `"reltol"`: to change the relative tolerance of the objective
4047+
"""
4048+
struct MultiObjectiveAttribute <: MOI.AbstractModelAttribute
4049+
index::Int
4050+
field::String
4051+
end
4052+
4053+
function MOI.set(model::Optimizer, attr::MultiObjectiveAttribute, value)
4054+
weight = CPX_NO_WEIGHT_CHANGE
4055+
priority = CPX_NO_PRIORITY_CHANGE
4056+
abstol = CPX_NO_ABSTOL_CHANGE
4057+
reltol = CPX_NO_RELTOL_CHANGE
4058+
if attr.field == "weight"
4059+
weight = value
4060+
elseif attr.field == "priority"
4061+
priority = value
4062+
elseif attr.field == "abstol"
4063+
abstol = value
4064+
elseif attr.field == "reltol"
4065+
reltol = value
4066+
else
4067+
throw(MOI.UnsupportedAttribute(attr))
4068+
end
4069+
ret = CPXmultiobjchgattribs(
4070+
model.env,
4071+
model.lp,
4072+
Cint(attr.index - 1),
4073+
CPX_NO_OFFSET_CHANGE,
4074+
weight,
4075+
priority,
4076+
abstol,
4077+
reltol,
4078+
C_NULL,
4079+
)
4080+
_check_ret(model, ret)
4081+
return
4082+
end

test/MathOptInterface/MOI_wrapper.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,94 @@ function test_relative_gap_GetAttributeNotAllowed()
225225
return
226226
end
227227

228+
function test_multiobjective()
229+
model = CPLEX.Optimizer()
230+
MOI.set(model, MOI.Silent(), true)
231+
MOI.Utilities.loadfromstring!(
232+
model,
233+
"""
234+
variables: x, y
235+
minobjective: [2x + y, x + 3 * y]
236+
c1: x + y >= 1.0
237+
c2: 0.5 * x + 1.0 * y >= 0.75
238+
c3: x >= 0.0
239+
c4: y >= 0.25
240+
""",
241+
)
242+
MOI.optimize!(model)
243+
@test MOI.get(model, MOI.ObjectiveValue()) [1.5, 2.0]
244+
x = MOI.get(model, MOI.ListOfVariableIndices())
245+
@test MOI.get(model, MOI.VariablePrimal(), x) [0.5, 0.5]
246+
return
247+
end
248+
249+
function test_multiobjective_attributes()
250+
model = CPLEX.Optimizer()
251+
MOI.set(model, MOI.Silent(), true)
252+
MOI.Utilities.loadfromstring!(
253+
model,
254+
"""
255+
variables: x, y
256+
minobjective: [-2 * x + -1 * y, x + 3 * y]
257+
c1: x + y >= 1.0
258+
c2: 0.5 * x + 1.0 * y >= 0.75
259+
c3: x >= 0.0
260+
c4: y >= 0.25
261+
""",
262+
)
263+
MOI.set(model, CPLEX.MultiObjectiveAttribute(1, "reltol"), 0.0)
264+
MOI.set(model, CPLEX.MultiObjectiveAttribute(1, "abstol"), 0.0)
265+
MOI.set(model, CPLEX.MultiObjectiveAttribute(1, "weight"), -1.0)
266+
MOI.set(model, CPLEX.MultiObjectiveAttribute(1, "priority"), 1)
267+
MOI.set(model, CPLEX.MultiObjectiveAttribute(2, "priority"), 2)
268+
MOI.optimize!(model)
269+
@test MOI.get(model, MOI.ObjectiveValue()) [-2.25, 1.75]
270+
x = MOI.get(model, MOI.ListOfVariableIndices())
271+
@test MOI.get(model, MOI.VariablePrimal(), x) [1.0, 0.25]
272+
MOI.set(model, CPLEX.MultiObjectiveAttribute(1, "priority"), 2)
273+
MOI.set(model, CPLEX.MultiObjectiveAttribute(2, "priority"), 1)
274+
MOI.optimize!(model)
275+
@test MOI.get(model, MOI.ObjectiveValue()) [-1.0, 3.0]
276+
x = MOI.get(model, MOI.ListOfVariableIndices())
277+
@test MOI.get(model, MOI.VariablePrimal(), x) [0.0, 1.0]
278+
@test_throws(
279+
MOI.UnsupportedAttribute,
280+
MOI.set(model, CPLEX.MultiObjectiveAttribute(1, "bad_attr"), 0.0),
281+
)
282+
return
283+
end
284+
285+
function test_example_biobjective_knapsack()
286+
p1 = [77.0, 94, 71, 63, 96, 82, 85, 75, 72, 91, 99, 63, 84, 87, 79, 94, 90]
287+
p2 = [65.0, 90, 90, 77, 95, 84, 70, 94, 66, 92, 74, 97, 60, 60, 65, 97, 93]
288+
w = [80.0, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91]
289+
model = CPLEX.Optimizer()
290+
x = MOI.add_variables(model, length(w))
291+
MOI.add_constraint.(model, x, MOI.ZeroOne())
292+
MOI.add_constraint(model, w' * x, MOI.LessThan(900.0))
293+
obj_f = MOI.Utilities.operate(vcat, Float64, p1' * x, p2' * x)
294+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
295+
MOI.set(model, MOI.ObjectiveFunction{typeof(obj_f)}(), obj_f)
296+
MOI.optimize!(model)
297+
results = Dict(
298+
[955.0, 906.0] => [2, 3, 5, 6, 9, 10, 11, 14, 15, 16, 17],
299+
[948.0, 939.0] => [1, 2, 3, 5, 6, 8, 10, 11, 15, 16, 17],
300+
[934.0, 971.0] => [2, 3, 5, 6, 8, 10, 11, 12, 15, 16, 17],
301+
[918.0, 983.0] => [2, 3, 4, 5, 6, 8, 10, 11, 12, 16, 17],
302+
)
303+
found_non_dominated_point = false
304+
for i in 1:MOI.get(model, MOI.ResultCount())
305+
X = findall(elt -> elt > 0.9, MOI.get.(model, MOI.VariablePrimal(i), x))
306+
Y = MOI.get(model, MOI.ObjectiveValue(i))
307+
if haskey(results, Y)
308+
@test results[Y] == X
309+
found_non_dominated_point = true
310+
end
311+
end
312+
@test found_non_dominated_point
313+
return
314+
end
315+
228316
end # module TestMOIwrapper
229317

230318
TestMOIwrapper.runtests()

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/jump-dev/CPLEX.jl/commit/1f3359bc1deec7b291116ebdd70ad358ccbcde1e

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy