Skip to content

Commit 43e4e1a

Browse files
authored
Merge pull request #1086 from martinbiel/uf_objective
Add support for custom objective functions in UniversalFallback.
2 parents 3f872f9 + 782aab2 commit 43e4e1a

2 files changed

Lines changed: 103 additions & 9 deletions

File tree

src/Utilities/universalfallback.jl

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ optimizer bridges should be used instead.
1313
"""
1414
mutable struct UniversalFallback{MT} <: MOI.ModelLike
1515
model::MT
16+
objective::Union{MOI.AbstractScalarFunction, Nothing}
1617
constraints::OrderedDict{Tuple{DataType, DataType}, OrderedDict} # See https://github.com/JuliaOpt/JuMP.jl/issues/1152 and https://github.com/JuliaOpt/JuMP.jl/issues/2238
1718
nextconstraintid::Int64
1819
con_to_name::Dict{CI, String}
@@ -23,6 +24,7 @@ mutable struct UniversalFallback{MT} <: MOI.ModelLike
2324
conattr::Dict{MOI.AbstractConstraintAttribute, Dict{CI, Any}}
2425
function UniversalFallback{MT}(model::MOI.ModelLike) where {MT}
2526
new{typeof(model)}(model,
27+
nothing,
2628
OrderedDict{Tuple{DataType, DataType}, OrderedDict}(),
2729
0,
2830
Dict{CI, String}(),
@@ -39,10 +41,11 @@ function Base.show(io::IO, U::UniversalFallback)
3941
s(n) = n == 1 ? "" : "s"
4042
indent = " "^get(io, :indent, 0)
4143
MOIU.print_with_acronym(io, summary(U))
44+
!(U.objective === nothing) && print(io, "\n$(indent)with objective")
4245
for (attr, name) in ( (U.constraints, "constraint"),
43-
(U.optattr, "optimizer attribute"),
44-
(U.modattr, "model attribute"),
45-
(U.varattr, "variable attribute"),
46+
(U.optattr, "optimizer attribute"),
47+
(U.modattr, "model attribute"),
48+
(U.varattr, "variable attribute"),
4649
(U.conattr, "constraint attribute") )
4750
n = length(attr)
4851
if n > 0
@@ -54,11 +57,12 @@ function Base.show(io::IO, U::UniversalFallback)
5457
end
5558

5659
function MOI.is_empty(uf::UniversalFallback)
57-
return MOI.is_empty(uf.model) && isempty(uf.constraints) &&
60+
return MOI.is_empty(uf.model) && uf.objective === nothing && isempty(uf.constraints) &&
5861
isempty(uf.modattr) && isempty(uf.varattr) && isempty(uf.conattr)
5962
end
6063
function MOI.empty!(uf::UniversalFallback)
6164
MOI.empty!(uf.model)
65+
uf.objective = nothing
6266
empty!(uf.constraints)
6367
uf.nextconstraintid = 0
6468
empty!(uf.con_to_name)
@@ -156,6 +160,9 @@ function MOI.delete(uf::UniversalFallback, vi::VI)
156160
for d in values(uf.varattr)
157161
delete!(d, vi)
158162
end
163+
if uf.objective !== nothing
164+
uf.objective = remove_variable(uf.objective, vi)
165+
end
159166
for (_, constraints) in uf.constraints
160167
_remove_variable(uf, constraints, vi)
161168
end
@@ -167,6 +174,9 @@ function MOI.delete(uf::UniversalFallback, vis::Vector{VI})
167174
delete!(d, vi)
168175
end
169176
end
177+
if uf.objective !== nothing
178+
uf.objective = remove_variable(uf.objective, vis)
179+
end
170180
for (_, constraints) in uf.constraints
171181
_remove_vector_of_variables(uf, constraints, vis)
172182
for vi in vis
@@ -248,6 +258,9 @@ function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfOptimizerAttributesS
248258
end
249259
function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfModelAttributesSet)
250260
list = MOI.get(uf.model, listattr)
261+
if uf.objective !== nothing
262+
push!(list, MOI.ObjectiveFunction{typeof(uf.objective)}())
263+
end
251264
for attr in keys(uf.modattr)
252265
push!(list, attr)
253266
end
@@ -268,6 +281,54 @@ function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfConstraintAttributes
268281
return list
269282
end
270283

284+
# Objective
285+
function MOI.set(uf::UniversalFallback, attr::MOI.ObjectiveSense,
286+
sense::MOI.OptimizationSense) where T
287+
if sense == MOI.FEASIBILITY_SENSE
288+
uf.objective = nothing
289+
end
290+
MOI.set(uf.model, attr, sense)
291+
end
292+
function MOI.get(uf::UniversalFallback,
293+
attr::MOI.ObjectiveFunctionType)
294+
if uf.objective === nothing
295+
return MOI.get(uf.model, attr)
296+
else
297+
return typeof(uf.objective)
298+
end
299+
end
300+
function MOI.get(uf::UniversalFallback,
301+
attr::MOI.ObjectiveFunction{F})::F where F
302+
if uf.objective === nothing
303+
return MOI.get(uf.model, attr)
304+
else
305+
return uf.objective
306+
end
307+
end
308+
function MOI.set(uf::UniversalFallback,
309+
attr::MOI.ObjectiveFunction,
310+
func::MOI.AbstractScalarFunction)
311+
if MOI.supports(uf.model, attr)
312+
MOI.set(uf.model, attr, func)
313+
# Clear any fallback objective
314+
uf.objective = nothing
315+
else
316+
uf.objective = copy(func)
317+
# Clear any `model` objective
318+
sense = MOI.get(uf.model, MOI.ObjectiveSense())
319+
MOI.set(uf.model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
320+
MOI.set(uf.model, MOI.ObjectiveSense(), sense)
321+
end
322+
end
323+
324+
function MOI.modify(uf::UniversalFallback, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) where F
325+
if uf.objective === nothing
326+
MOI.modify(uf.model, obj, change)
327+
else
328+
uf.objective = modify_function(uf.objective, change)
329+
end
330+
end
331+
271332
# Name
272333
# The names of constraints not supported by `uf.model` need to be handled
273334
function MOI.set(uf::UniversalFallback, attr::MOI.ConstraintName, ci::CI{F, S}, name::String) where {F, S}
@@ -430,9 +491,6 @@ function MOI.set(uf::UniversalFallback, ::MOI.ConstraintSet, ci::CI{F,S}, set::S
430491
end
431492
end
432493

433-
# Objective
434-
MOI.modify(uf::UniversalFallback, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) = MOI.modify(uf.model, obj, change)
435-
436494
# Variables
437495
MOI.add_variable(uf::UniversalFallback) = MOI.add_variable(uf.model)
438496
MOI.add_variables(uf::UniversalFallback, n) = MOI.add_variables(uf.model, n)

test/Utilities/universalfallback.jl

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ end
5252

5353
struct UnknownOptimizerAttribute <: MOI.AbstractOptimizerAttribute end
5454

55-
# A few constraint types are supported to test both the fallback and the
55+
# A few objective/constraint types are supported to test both the fallback and the
5656
# delegation to the internal model
5757
@MOIU.model(ModelForUniversalFallback,
5858
(),
@@ -63,6 +63,11 @@ struct UnknownOptimizerAttribute <: MOI.AbstractOptimizerAttribute end
6363
(MOI.ScalarAffineFunction,),
6464
(),
6565
())
66+
function MOI.supports(
67+
::ModelForUniversalFallback{T},
68+
::Type{MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}}) where T
69+
return false
70+
end
6671
function MOI.supports_constraint(
6772
::ModelForUniversalFallback{T}, ::Type{MOI.SingleVariable},
6873
::Type{<:Union{MOI.EqualTo{T}, MOI.GreaterThan{T}, MOI.Interval{T},
@@ -123,6 +128,37 @@ y, z = MOI.add_variables(uf, 2)
123128
listattr = MOI.ListOfVariableAttributesSet()
124129
test_varconattrs(uf, model, attr, listattr, VI, MOI.add_variable, x, y, z)
125130
end
131+
@testset "Objective Attribute" begin
132+
global x, y, z
133+
_single(vi) = MOI.SingleVariable(vi)
134+
_affine(vi) = convert(MOI.ScalarAffineFunction{Float64}, MOI.SingleVariable(vi))
135+
function _add_single_objective(vi)
136+
return MOI.set(uf, MOI.ObjectiveFunction{MOI.SingleVariable}(), _single(vi))
137+
end
138+
function _add_affine_objective(vi)
139+
return MOI.set(uf, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), _affine(vi))
140+
end
141+
@testset "Supported objective" begin
142+
F = MOI.SingleVariable
143+
@test MOI.supports(model, MOI.ObjectiveFunction{F}())
144+
_add_single_objective(x)
145+
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) _single(x)
146+
MOI.set(uf, MOI.ObjectiveFunction{F}(), _single(y))
147+
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) _single(y)
148+
end
149+
@testset "Unsupported objective" begin
150+
F = MOI.ScalarAffineFunction{Float64}
151+
@test !MOI.supports(model, MOI.ObjectiveFunction{F})
152+
@test MOI.supports(uf, MOI.ObjectiveFunction{F}())
153+
_add_affine_objective(x)
154+
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) _affine(x)
155+
MOI.modify(uf, MOI.ObjectiveFunction{F}(), MOI.ScalarCoefficientChange(y, 1.))
156+
fx = MOI.SingleVariable(x)
157+
fy = MOI.SingleVariable(y)
158+
obj = 1fx + 1fy
159+
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) obj
160+
end
161+
end
126162
@testset "Constraint Attribute" begin
127163
global x, y, z
128164
_affine(vi) = convert(MOI.ScalarAffineFunction{Float64}, MOI.SingleVariable(vi))
@@ -221,7 +257,7 @@ end
221257
cy2 = MOI.add_constraint(uf, _affine(y), sets[2])
222258
# check that the constraint types are in the order they were added in
223259
@test MOI.get(uf, MOI.ListOfConstraints()) == [(F, typeof(sets[1])), (F, typeof(sets[2]))]
224-
# check that the constraints given the constraint type are in the order they were added in
260+
# check that the constraints given the constraint type are in the order they were added in
225261
@test MOI.get(uf, MOI.ListOfConstraintIndices{F, typeof(sets[1])}()) == [MOI.ConstraintIndex{F, typeof(sets[1])}(1), MOI.ConstraintIndex{F, typeof(sets[1])}(3)]
226262
@test MOI.get(uf, MOI.ListOfConstraintIndices{F, typeof(sets[2])}()) == [MOI.ConstraintIndex{F, typeof(sets[2])}(2), MOI.ConstraintIndex{F, typeof(sets[2])}(4)]
227263
end

0 commit comments

Comments
 (0)