Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 65 additions & 7 deletions src/Utilities/universalfallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ optimizer bridges should be used instead.
"""
mutable struct UniversalFallback{MT} <: MOI.ModelLike
model::MT
objective::Union{MOI.AbstractScalarFunction, Nothing}
constraints::OrderedDict{Tuple{DataType, DataType}, OrderedDict} # See https://github.com/JuliaOpt/JuMP.jl/issues/1152 and https://github.com/JuliaOpt/JuMP.jl/issues/2238
nextconstraintid::Int64
con_to_name::Dict{CI, String}
Expand All @@ -23,6 +24,7 @@ mutable struct UniversalFallback{MT} <: MOI.ModelLike
conattr::Dict{MOI.AbstractConstraintAttribute, Dict{CI, Any}}
function UniversalFallback{MT}(model::MOI.ModelLike) where {MT}
new{typeof(model)}(model,
nothing,
OrderedDict{Tuple{DataType, DataType}, OrderedDict}(),
0,
Dict{CI, String}(),
Expand All @@ -39,10 +41,11 @@ function Base.show(io::IO, U::UniversalFallback)
s(n) = n == 1 ? "" : "s"
indent = " "^get(io, :indent, 0)
MOIU.print_with_acronym(io, summary(U))
!(U.objective === nothing) && print(io, "\n$(indent)with objective")
for (attr, name) in ( (U.constraints, "constraint"),
(U.optattr, "optimizer attribute"),
(U.modattr, "model attribute"),
(U.varattr, "variable attribute"),
(U.optattr, "optimizer attribute"),
(U.modattr, "model attribute"),
(U.varattr, "variable attribute"),
(U.conattr, "constraint attribute") )
n = length(attr)
if n > 0
Expand All @@ -54,11 +57,12 @@ function Base.show(io::IO, U::UniversalFallback)
end

function MOI.is_empty(uf::UniversalFallback)
return MOI.is_empty(uf.model) && isempty(uf.constraints) &&
return MOI.is_empty(uf.model) && uf.objective === nothing && isempty(uf.constraints) &&
isempty(uf.modattr) && isempty(uf.varattr) && isempty(uf.conattr)
end
function MOI.empty!(uf::UniversalFallback)
MOI.empty!(uf.model)
uf.objective = nothing
empty!(uf.constraints)
uf.nextconstraintid = 0
empty!(uf.con_to_name)
Expand Down Expand Up @@ -156,6 +160,9 @@ function MOI.delete(uf::UniversalFallback, vi::VI)
for d in values(uf.varattr)
delete!(d, vi)
end
if uf.objective !== nothing
uf.objective = remove_variable(uf.objective, vi)
end
for (_, constraints) in uf.constraints
_remove_variable(uf, constraints, vi)
end
Expand All @@ -167,6 +174,9 @@ function MOI.delete(uf::UniversalFallback, vis::Vector{VI})
delete!(d, vi)
end
end
if uf.objective !== nothing
uf.objective = remove_variable(uf.objective, vis)
end
for (_, constraints) in uf.constraints
_remove_vector_of_variables(uf, constraints, vis)
for vi in vis
Expand Down Expand Up @@ -248,6 +258,9 @@ function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfOptimizerAttributesS
end
function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfModelAttributesSet)
list = MOI.get(uf.model, listattr)
if uf.objective !== nothing
push!(list, MOI.ObjectiveFunction{typeof(uf.objective)}())
end
for attr in keys(uf.modattr)
push!(list, attr)
end
Expand All @@ -268,6 +281,54 @@ function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfConstraintAttributes
return list
end

# Objective
function MOI.set(uf::UniversalFallback, attr::MOI.ObjectiveSense,
sense::MOI.OptimizationSense) where T
if sense == MOI.FEASIBILITY_SENSE
uf.objective = nothing
end
MOI.set(uf.model, attr, sense)
end
function MOI.get(uf::UniversalFallback,
attr::MOI.ObjectiveFunctionType)
if uf.objective === nothing
return MOI.get(uf.model, attr)
else
return typeof(uf.objective)
end
end
function MOI.get(uf::UniversalFallback,
attr::MOI.ObjectiveFunction{F})::F where F
if uf.objective === nothing
return MOI.get(uf.model, attr)
else
return uf.objective
end
end
function MOI.set(uf::UniversalFallback,
attr::MOI.ObjectiveFunction,
func::MOI.AbstractScalarFunction)
if MOI.supports(uf.model, attr)
MOI.set(uf.model, attr, func)
# Clear any fallback objective
uf.objective = nothing
else
uf.objective = copy(func)
# Clear any `model` objective
sense = MOI.get(uf.model, MOI.ObjectiveSense())
MOI.set(uf.model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
MOI.set(uf.model, MOI.ObjectiveSense(), sense)
end
end

function MOI.modify(uf::UniversalFallback, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) where F
if uf.objective === nothing
MOI.modify(uf.model, obj, change)
else
uf.objective = modify_function(uf.objective, change)
end
end

# Name
# The names of constraints not supported by `uf.model` need to be handled
function MOI.set(uf::UniversalFallback, attr::MOI.ConstraintName, ci::CI{F, S}, name::String) where {F, S}
Expand Down Expand Up @@ -430,9 +491,6 @@ function MOI.set(uf::UniversalFallback, ::MOI.ConstraintSet, ci::CI{F,S}, set::S
end
end

# Objective
MOI.modify(uf::UniversalFallback, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) = MOI.modify(uf.model, obj, change)

# Variables
MOI.add_variable(uf::UniversalFallback) = MOI.add_variable(uf.model)
MOI.add_variables(uf::UniversalFallback, n) = MOI.add_variables(uf.model, n)
40 changes: 38 additions & 2 deletions test/Utilities/universalfallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ end

struct UnknownOptimizerAttribute <: MOI.AbstractOptimizerAttribute end

# A few constraint types are supported to test both the fallback and the
# A few objective/constraint types are supported to test both the fallback and the
# delegation to the internal model
@MOIU.model(ModelForUniversalFallback,
(),
Expand All @@ -63,6 +63,11 @@ struct UnknownOptimizerAttribute <: MOI.AbstractOptimizerAttribute end
(MOI.ScalarAffineFunction,),
(),
())
function MOI.supports(
::ModelForUniversalFallback{T},
::Type{MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}}) where T
return false
end
function MOI.supports_constraint(
::ModelForUniversalFallback{T}, ::Type{MOI.SingleVariable},
::Type{<:Union{MOI.EqualTo{T}, MOI.GreaterThan{T}, MOI.Interval{T},
Expand Down Expand Up @@ -123,6 +128,37 @@ y, z = MOI.add_variables(uf, 2)
listattr = MOI.ListOfVariableAttributesSet()
test_varconattrs(uf, model, attr, listattr, VI, MOI.add_variable, x, y, z)
end
@testset "Objective Attribute" begin
global x, y, z
_single(vi) = MOI.SingleVariable(vi)
_affine(vi) = convert(MOI.ScalarAffineFunction{Float64}, MOI.SingleVariable(vi))
function _add_single_objective(vi)
return MOI.set(uf, MOI.ObjectiveFunction{MOI.SingleVariable}(), _single(vi))
end
function _add_affine_objective(vi)
return MOI.set(uf, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), _affine(vi))
end
@testset "Supported objective" begin
F = MOI.SingleVariable
@test MOI.supports(model, MOI.ObjectiveFunction{F}())
_add_single_objective(x)
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) ≈ _single(x)
MOI.set(uf, MOI.ObjectiveFunction{F}(), _single(y))
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) ≈ _single(y)
end
@testset "Unsupported objective" begin
F = MOI.ScalarAffineFunction{Float64}
@test !MOI.supports(model, MOI.ObjectiveFunction{F})
@test MOI.supports(uf, MOI.ObjectiveFunction{F}())
_add_affine_objective(x)
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) ≈ _affine(x)
MOI.modify(uf, MOI.ObjectiveFunction{F}(), MOI.ScalarCoefficientChange(y, 1.))
fx = MOI.SingleVariable(x)
fy = MOI.SingleVariable(y)
obj = 1fx + 1fy
@test MOI.get(uf, MOI.ObjectiveFunction{F}()) ≈ obj
end
end
@testset "Constraint Attribute" begin
global x, y, z
_affine(vi) = convert(MOI.ScalarAffineFunction{Float64}, MOI.SingleVariable(vi))
Expand Down Expand Up @@ -221,7 +257,7 @@ end
cy2 = MOI.add_constraint(uf, _affine(y), sets[2])
# check that the constraint types are in the order they were added in
@test MOI.get(uf, MOI.ListOfConstraints()) == [(F, typeof(sets[1])), (F, typeof(sets[2]))]
# check that the constraints given the constraint type are in the order they were added in
# check that the constraints given the constraint type are in the order they were added in
@test MOI.get(uf, MOI.ListOfConstraintIndices{F, typeof(sets[1])}()) == [MOI.ConstraintIndex{F, typeof(sets[1])}(1), MOI.ConstraintIndex{F, typeof(sets[1])}(3)]
@test MOI.get(uf, MOI.ListOfConstraintIndices{F, typeof(sets[2])}()) == [MOI.ConstraintIndex{F, typeof(sets[2])}(2), MOI.ConstraintIndex{F, typeof(sets[2])}(4)]
end
Expand Down