diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index 39cf7c45e6..cdad5cf3de 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -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} @@ -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}(), @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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} @@ -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) diff --git a/test/Utilities/universalfallback.jl b/test/Utilities/universalfallback.jl index 38fc8df1df..1aba511f70 100644 --- a/test/Utilities/universalfallback.jl +++ b/test/Utilities/universalfallback.jl @@ -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, (), @@ -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}, @@ -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)) @@ -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