From 27e2450839143cca767fd02976786ee7a8ccf65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 24 Feb 2021 00:29:49 +0100 Subject: [PATCH] Fixes --- src/Utilities/Utilities.jl | 1 + src/Utilities/model.jl | 513 +++++++++---------------- src/Utilities/struct_of_constraints.jl | 266 +++++++++++++ src/Utilities/universalfallback.jl | 218 +++++------ src/Utilities/vector_of_constraints.jl | 80 ++-- test/Utilities/universalfallback.jl | 6 +- 6 files changed, 605 insertions(+), 479 deletions(-) create mode 100644 src/Utilities/struct_of_constraints.jl diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index d2d704450d..822a5c6c04 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -58,6 +58,7 @@ include("results.jl") include("variables.jl") include("vector_of_constraints.jl") +include("struct_of_constraints.jl") include("model.jl") include("parser.jl") include("mockoptimizer.jl") diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index c21169900d..93c681cdf4 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -62,14 +62,6 @@ function _delete_variable( vi::MOI.VariableIndex, ) where {T} MOI.throw_if_not_valid(model, vi) - # If a variable is removed, the `VectorOfVariables` constraints using this - # variable only need to be removed too. `vov_to_remove` is the list of - # indices of the `VectorOfVariables` constraints of `vi`. - vov_to_remove = - broadcastvcat(constrs -> _vector_of_variables_with(constrs, vi), model) - for ci in vov_to_remove - MOI.delete(model, ci) - end model.single_variable_mask[vi.value] = 0x0 if model.variable_indices === nothing model.variable_indices = @@ -113,11 +105,15 @@ function _delete_variable( ) end function MOI.delete(model::AbstractModel, vi::MOI.VariableIndex) + vis = [vi] + _throw_if_cannot_delete(model.constraints, vis, vis) _delete_variable(model, vi) - # `VectorOfVariables` constraints with sets not supporting dimension update - # were either deleted or an error was thrown. The rest is modified now. - broadcastcall(constrs -> _remove_variable(constrs, vi), model) - return model.objective = remove_variable(model.objective, vi) + _deleted_constraints(model.constraints, vi) do ci + delete!(model.con_to_name, ci) + end + model.objective = remove_variable(model.objective, vi) + model.name_to_con = nothing + return end function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex}) if isempty(vis) @@ -125,21 +121,17 @@ function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex}) # at least one variable need to be deleted. return end - # Delete `VectorOfVariables(vis)` constraints as otherwise, it will error - # when removing variables one by one. - vov_to_remove = - broadcastvcat(constrs -> _vector_of_variables_with(constrs, vis), model) - for ci in vov_to_remove - MOI.delete(model, ci) + _throw_if_cannot_delete(model.constraints, vis, Set(vis)) + _deleted_constraints(model.constraints, vis) do ci + delete!(model.con_to_name, ci) end for vi in vis _delete_variable(model, vi) end - # `VectorOfVariables` constraints with sets not supporting dimension update - # were either deleted or an error was thrown. The rest is modified now. keep(vi::MOI.VariableIndex) = vi in model.variable_indices model.objective = filter_variables(keep, model.objective) - return broadcastcall(constrs -> _filter_variables(keep, constrs), model) + model.name_to_con = nothing + return end function MOI.is_valid( @@ -151,12 +143,8 @@ function MOI.is_valid( model.single_variable_mask[ci.value] & single_variable_flag(S), ) end -function MOI.is_valid(model::AbstractModel, ci::CI{F,S}) where {F,S} - if MOI.supports_constraint(model, F, S) - return MOI.is_valid(constraints(model, ci), ci) - else - return false - end +function MOI.is_valid(model::AbstractModel, ci::MOI.ConstraintIndex) + return MOI.is_valid(model.constraints, ci) end function MOI.is_valid(model::AbstractModel, vi::VI) if model.variable_indices === nothing @@ -460,6 +448,13 @@ function MOI.supports_constraint( ) where {T} return true end +function MOI.supports_constraint( + model::AbstractModel, + ::Type{F}, + ::Type{S}, +) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet} + return MOI.supports_constraint(model.constraints, F, S) +end function MOI.add_constraint( model::AbstractModel{T}, @@ -483,26 +478,11 @@ function MOI.add_constraint( end function MOI.add_constraint(model::AbstractModel, func::MOI.AbstractFunction, set::MOI.AbstractSet) - if MOI.supports_constraint(model, typeof(func), typeof(set)) - return MOI.add_constraint(constraints(model, typeof(func), typeof(set)), func, set) - else - throw(MOI.UnsupportedConstraint{typeof(func),typeof(set)}()) - end -end -function constraints( - model::AbstractModel, - ci::MOI.ConstraintIndex{F,S} -) where {F,S} - if !MOI.supports_constraint(model, F, S) - throw(MOI.InvalidIndex(ci)) - end - return constraints(model, F, S) + return MOI.add_constraint(model.constraints, func, set) end + function MOI.get(model::AbstractModel, attr::Union{MOI.AbstractFunction, MOI.AbstractSet}, ci::MOI.ConstraintIndex) - return MOI.get(constraints(model, ci), attr, ci) -end -function MOI.modify(model::AbstractModel, ci::MOI.ConstraintIndex, change) - return MOI.modify(constraints(model, ci), ci, change) + return MOI.get(model.constraints, attr, ci) end function _delete_constraint( @@ -513,7 +493,7 @@ function _delete_constraint( return model.single_variable_mask[ci.value] &= ~single_variable_flag(S) end function _delete_constraint(model::AbstractModel, ci::MOI.ConstraintIndex) - return MOI.delete(constraints(model, ci), ci) + return MOI.delete(model.constraints, ci) end function MOI.delete(model::AbstractModel, ci::MOI.ConstraintIndex) _delete_constraint(model, ci) @@ -526,7 +506,7 @@ function MOI.modify( ci::MOI.ConstraintIndex, change::MOI.AbstractFunctionModification, ) - return MOI.modify(constraints(model, ci), ci, change) + return MOI.modify(model.constraints, ci, change) end function MOI.set( @@ -537,14 +517,6 @@ function MOI.set( ) return throw(MOI.SettingSingleVariableFunctionNotAllowed()) end -function MOI.set( - model::AbstractModel, - attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, - ci::MOI.ConstraintIndex, - func_or_set, -) - return MOI.set(constraints(model, ci), attr, ci, func_or_set) -end function MOI.set( model::AbstractModel{T}, ::MOI.ConstraintSet, @@ -562,11 +534,11 @@ function MOI.set( end function MOI.set( model::AbstractModel, - attr::MOI.ConstraintSet, - ci::MOI.ConstraintIndex{F,S}, - set::S, -) where {F,S} - return MOI.set(constraints(model, ci), attr, ci, set) + attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, + ci::MOI.ConstraintIndex, + func_or_set, +) + return MOI.set(model.constraints, attr, ci, func_or_set) end function MOI.get( @@ -577,11 +549,7 @@ function MOI.get( return count(mask -> !iszero(flag & mask), model.single_variable_mask) end function MOI.get(model::AbstractModel, noc::MOI.NumberOfConstraints{F,S}) where {F,S} - if MOI.supports_constraint(model, F, S) - return MOI.get(constraints(model, F, S), noc) - else - return 0 - end + return MOI.get(model.constraints, noc) end function _add_contraint_type( @@ -596,9 +564,7 @@ function _add_contraint_type( return end function MOI.get(model::AbstractModel{T}, loc::MOI.ListOfConstraints) where {T} - list = broadcastvcat(model) do v - MOI.get(v, loc) - end + list = copy(MOI.get(model.constraints, loc)) for S in ( MOI.EqualTo{T}, MOI.GreaterThan{T}, @@ -629,11 +595,7 @@ function MOI.get( end function MOI.get(model::AbstractModel, loc::MOI.ListOfConstraintIndices{F,S}) where {F,S} - if MOI.supports_constraint(model, F, S) - return MOI.get(constraints(model, F, S), loc) - else - return MOI.ConstraintIndex{F,S}[] - end + return MOI.get(model.constraints, loc) end function MOI.get( @@ -649,12 +611,12 @@ function MOI.get( attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, ci::MOI.ConstraintIndex ) - return MOI.get(constraints(model, ci), attr, ci) + return MOI.get(model.constraints, attr, ci) end function _get_single_variable_set( model::AbstractModel, - S::Type{<:MOI.EqualTo}, + ::Type{<:MOI.EqualTo}, index, ) return MOI.EqualTo(model.lower_bound[index]) @@ -682,7 +644,7 @@ function _get_single_variable_set( return S(model.lower_bound[index], model.upper_bound[index]) end function _get_single_variable_set( - model::AbstractModel, + ::AbstractModel, S::Type{<:Union{MOI.Integer,MOI.ZeroOne}}, index, ) @@ -704,7 +666,7 @@ function MOI.is_empty(model::AbstractModel) isempty(model.objective.terms) && iszero(model.objective.constant) && iszero(model.num_variables_created) && - mapreduce_constraints(MOI.is_empty, &, model, true) + MOI.is_empty(model.constraints) end function MOI.empty!(model::AbstractModel{T}) where {T} model.name = "" @@ -721,7 +683,7 @@ function MOI.empty!(model::AbstractModel{T}) where {T} model.name_to_var = nothing empty!(model.con_to_name) model.name_to_con = nothing - broadcastcall(MOI.empty!, model) + MOI.empty!(model.constraints) end @@ -756,100 +718,7 @@ function load_constraint( ::MOI.AbstractSet, ) end -# Can be used to access constraints of a model -""" -broadcastcall(f::Function, model::AbstractModel) - -Calls `f(contrs)` for every vector `constrs::Vector{ConstraintIndex{F, S}, F, S}` of the model. - -# Examples - -To add all constraints of the model to a solver `solver`, one can do -```julia -_addcon(solver, ci, f, s) = MOI.add_constraint(solver, f, s) -function _addcon(solver, constrs::Vector) - for constr in constrs - _addcon(solver, constr...) - end -end -MOIU.broadcastcall(constrs -> _addcon(solver, constrs), model) -``` -""" -function broadcastcall end - -""" -broadcastvcat(f::Function, model::AbstractModel) - -Calls `f(contrs)` for every vector `constrs::Vector{ConstraintIndex{F, S}, F, S}` of the model and concatenate the results with `vcat` (this is used internally for `ListOfConstraints`). - -# Examples - -To get the list of all functions: -```julia -_getfun(ci, f, s) = f -_getfun(cindices::Tuple) = _getfun(cindices...) -_getfuns(constrs::Vector) = _getfun.(constrs) -MOIU.broadcastvcat(_getfuns, model) -``` -""" -function broadcastvcat end - -function mapreduce_constraints end - # Macro to generate Model -abstract type Constraints{F} end - -abstract type SymbolFS end -struct SymbolFun <: SymbolFS - s::Union{Symbol,Expr} - typed::Bool - cname::Expr # `esc(scname)` or `esc(vcname)` -end -struct SymbolSet <: SymbolFS - s::Union{Symbol,Expr} - typed::Bool -end - -# QuoteNode prevents s from being interpolated and keeps it as a symbol -# Expr(:., MOI, s) would be MOI.s -# Expr(:., MOI, $s) would be Expr(:., MOI, EqualTo) -# Expr(:., MOI, :($s)) would be Expr(:., MOI, :EqualTo) -# Expr(:., MOI, :($(QuoteNode(s)))) is Expr(:., MOI, :(:EqualTo)) <- what we want - -# (MOI, :Zeros) -> :(MOI.Zeros) -# (:Zeros) -> :(MOI.Zeros) -_set(s::SymbolSet) = esc(s.s) -_fun(s::SymbolFun) = esc(s.s) -function _typedset(s::SymbolSet) - if s.typed - :($(_set(s)){T}) - else - _set(s) - end -end -function _typedfun(s::SymbolFun) - if s.typed - :($(_fun(s)){T}) - else - _fun(s) - end -end - -# Base.lowercase is moved to Unicode.lowercase in Julia v0.7 -using Unicode - -_field(s::SymbolFS) = Symbol(replace(lowercase(string(s.s)), "." => "_")) - -_getC(s::SymbolSet) = :(VectorOfConstraints{F,$(_typedset(s))}) -_getC(s::SymbolFun) = _typedfun(s) - -_getCV(s::SymbolSet) = :($(_getC(s))()) -_getCV(s::SymbolFun) = :($(s.cname){T,$(_getC(s))}()) - -_callfield(f, s::SymbolFS) = :($f(model.$(_field(s)))) -_broadcastfield(b, s::SymbolFS) = :($b(f, model.$(_field(s)))) -_mapreduce_field(s::SymbolFS) = :(cur = $MOIU.mapreduce_constraints(f, op, model.$(_field(s)), cur)) -_mapreduce_constraints(s::SymbolFS) = :(cur = op(cur, f(model.$(_field(s))))) # This macro is for expert/internal use only. Prefer the concrete Model type # instantiated below. @@ -984,184 +853,160 @@ macro model( vcname = esc(Symbol(string(model_name) * "VectorConstraints")) esc_model_name = esc(model_name) - header = if is_optimizer - :($(esc(model_name)){T} <: AbstractOptimizer{T}) - else - :($(esc(model_name)){T} <: AbstractModelLike{T}) - end + # TODO if there is only one function or one set, remove the layer scalar_funs = [ - SymbolFun.(sf.args, false, Ref(scname)) - SymbolFun.(sft.args, true, Ref(scname)) + SymbolFun.(sf.args, false) + SymbolFun.(sft.args, true) ] vector_funs = [ - SymbolFun.(vf.args, false, Ref(vcname)) - SymbolFun.(vft.args, true, Ref(vcname)) + SymbolFun.(vf.args, false) + SymbolFun.(vft.args, true) ] funs = [scalar_funs; vector_funs] - - scalarconstraints = :( - struct $scname{T,F<:$MOI.AbstractScalarFunction} <: Constraints{F} end - ) - vectorconstraints = :( - struct $vcname{T,F<:$MOI.AbstractVectorFunction} <: Constraints{F} end - ) - for (c, sets) in - ((scalarconstraints, scalar_sets), (vectorconstraints, vector_sets)) - for s in sets - field = _field(s) - push!(c.args[3].args, :($field::$(_getC(s)))) - end - end - - modeldef = quote - mutable struct $header - name::String - senseset::Bool - sense::$MOI.OptimizationSense - objectiveset::Bool - objective::Union{ - $MOI.SingleVariable, - $MOI.ScalarAffineFunction{T}, - $MOI.ScalarQuadraticFunction{T}, - } - num_variables_created::Int64 - # If nothing, no variable has been deleted so the indices of the - # variables are VI.(1:num_variables_created) - variable_indices::Union{Nothing,Set{$VI}} - # Union of flags of `S` such that a `SingleVariable`-in-`S` - # constraint was added to the model and not deleted yet. - single_variable_mask::Vector{UInt8} - # Lower bound set by `SingleVariable`-in-`S` where `S`is - # `GreaterThan{T}`, `EqualTo{T}` or `Interval{T}`. - lower_bound::Vector{T} - # Lower bound set by `SingleVariable`-in-`S` where `S`is - # `LessThan{T}`, `EqualTo{T}` or `Interval{T}`. - upper_bound::Vector{T} - var_to_name::Dict{$VI,String} - # If `nothing`, the dictionary hasn't been constructed yet. - name_to_var::Union{Dict{String,$VI},Nothing} - con_to_name::Dict{$CI,String} - name_to_con::Union{Dict{String,$CI},Nothing} - # A useful dictionary for extensions to store things. These are - # _not_ copied between models! - ext::Dict{Symbol,Any} - end - end - for f in funs - cname = f.cname - field = _field(f) - push!(modeldef.args[2].args[3].args, :($field::$cname{T,$(_getC(f))})) - end - - code = quote - function $MOIU.broadcastcall(f::Function, model::$esc_model_name) - return $(Expr(:block, _broadcastfield.(Ref(:(broadcastcall)), funs)...)) - end - function $MOIU.broadcastvcat(f::Function, model::$esc_model_name) - return vcat($(_broadcastfield.(Ref(:(broadcastvcat)), funs)...)) + set_struct_types = map(eachindex(funs)) do i + if i <= length(scalar_funs) + cname = scname + sets = scalar_sets + else + cname = vcname + sets = vector_sets end - function $MOIU.mapreduce_constraints(f::Function, op::Function, model::$esc_model_name, cur) - return $(Expr(:block, _mapreduce_field.(funs)...)) + voc = map(sets) do set + :(VectorOfConstraints{$(_typedfun(funs[i])),$(_typedset(set))}) end + t = :($cname{T}) + append!(t.args, voc) + return t end - for (cname, sets) in ((scname, scalar_sets), (vcname, vector_sets)) - code = quote - $code - function $MOIU.broadcastcall(f::Function, model::$cname) - return $(Expr(:block, _callfield.(:f, sets)...)) - end - function $MOIU.broadcastvcat(f::Function, model::$cname) - return vcat($(_callfield.(:f, sets)...)) - end - function $MOIU.mapreduce_constraints(f::Function, op::Function, model::$cname, cur) - return $(Expr(:block, _mapreduce_constraints.(sets)...)) - end - end + func_name = esc(Symbol(string(model_name) * "FunctionConstraints")) + func_typed = :($func_name{T}) + append!(func_typed.args, set_struct_types) + generic = if is_optimizer + :(GenericOptimizer{T, $func_typed}) + else + :(GenericModel{T, $func_typed}) end - - for (c, sets) in ((scname, scalar_sets), (vcname, vector_sets)) - for s in sets - set = _set(s) - field = _field(s) - code = quote - $code - function $MOIU.constraints( - model::$c, - ::Type{<:$set}, - ) where {F} - return model.$field - end - end - end + model_code = if is_optimizer + :(const $esc_model_name{T} = $generic) + else + :(const $esc_model_name{T} = $generic) end + return Expr( + :block, + struct_of_constraint_code(scname, scalar_sets), + struct_of_constraint_code(vcname, vector_sets), + struct_of_constraint_code(func_name, funs), + model_code, + ) +end - for f in funs - fun = _fun(f) - field = _field(f) - code = quote - $code - function $MOIU.constraints( - model::$esc_model_name, - ::Type{<:$fun}, - ::Type{S} - ) where S - return $MOIU.constraints(model.$field, S) - end - end +mutable struct GenericModel{T,C} <: AbstractModelLike{T} + name::String + senseset::Bool + sense::MOI.OptimizationSense + objectiveset::Bool + objective::Union{ + MOI.SingleVariable, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + } + num_variables_created::Int64 + # If nothing, no variable has been deleted so the indices of the + # variables are VI.(1:num_variables_created) + variable_indices::Union{Nothing,Set{VI}} + # Union of flags of `S` such that a `SingleVariable`-in-`S` + # constraint was added to the model and not deleted yet. + single_variable_mask::Vector{UInt8} + # Lower bound set by `SingleVariable`-in-`S` where `S`is + # `GreaterThan{T}`, `EqualTo{T}` or `Interval{T}`. + lower_bound::Vector{T} + # Lower bound set by `SingleVariable`-in-`S` where `S`is + # `LessThan{T}`, `EqualTo{T}` or `Interval{T}`. + upper_bound::Vector{T} + constraints::C + var_to_name::Dict{MOI.VariableIndex,String} + # If `nothing`, the dictionary hasn't been constructed yet. + name_to_var::Union{Dict{String,MOI.VariableIndex},Nothing} + con_to_name::Dict{MOI.ConstraintIndex,String} + name_to_con::Union{Dict{String,MOI.ConstraintIndex},Nothing} + # A useful dictionary for extensions to store things. These are + # _not_ copied between models! + ext::Dict{Symbol,Any} + function GenericModel{T,C}() where {T,C} + return new{T,C}( + EMPTYSTRING, + false, + MOI.FEASIBILITY_SENSE, + false, + zero(MOI.ScalarAffineFunction{T}), + 0, + nothing, + UInt8[], + T[], + T[], + C(), + Dict{MOI.VariableIndex,String}(), + nothing, + Dict{MOI.ConstraintIndex,String}(), + nothing, + Dict{Symbol,Any}(), + ) end +end - code = quote - $scalarconstraints - function $scname{T,F}() where {T,F} - return $scname{T,F}($(_getCV.(scalar_sets)...)) - end - - $vectorconstraints - function $vcname{T,F}() where {T,F} - return $vcname{T,F}($(_getCV.(vector_sets)...)) - end - - $modeldef - function $esc_model_name{T}() where {T} - return $esc_model_name{T}( - "", - false, - $MOI.FEASIBILITY_SENSE, - false, - $SAF{T}($MOI.ScalarAffineTerm{T}[], zero(T)), - 0, - nothing, - UInt8[], - T[], - T[], - Dict{$VI,String}(), - nothing, - Dict{$CI,String}(), - nothing, - Dict{Symbol,Any}(), - $(_getCV.(funs)...), - ) - end - - function $MOI.supports_constraint( - model::$esc_model_name{T}, - ::Type{<:Union{$(_typedfun.(scalar_funs)...)}}, - ::Type{<:Union{$(_typedset.(scalar_sets)...)}}, - ) where {T} - return true - end - function $MOI.supports_constraint( - model::$esc_model_name{T}, - ::Type{<:Union{$(_typedfun.(vector_funs)...)}}, - ::Type{<:Union{$(_typedset.(vector_sets)...)}}, - ) where {T} - return true - end - - $code +mutable struct GenericOptimizer{T,C} <: AbstractOptimizer{T} + name::String + senseset::Bool + sense::MOI.OptimizationSense + objectiveset::Bool + objective::Union{ + MOI.SingleVariable, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + } + num_variables_created::Int64 + # If nothing, no variable has been deleted so the indices of the + # variables are VI.(1:num_variables_created) + variable_indices::Union{Nothing,Set{VI}} + # Union of flags of `S` such that a `SingleVariable`-in-`S` + # constraint was added to the model and not deleted yet. + single_variable_mask::Vector{UInt8} + # Lower bound set by `SingleVariable`-in-`S` where `S`is + # `GreaterThan{T}`, `EqualTo{T}` or `Interval{T}`. + lower_bound::Vector{T} + # Lower bound set by `SingleVariable`-in-`S` where `S`is + # `LessThan{T}`, `EqualTo{T}` or `Interval{T}`. + upper_bound::Vector{T} + constraints::C + var_to_name::Dict{VI,String} + # If `nothing`, the dictionary hasn't been constructed yet. + name_to_var::Union{Dict{String,VI},Nothing} + con_to_name::Dict{CI,String} + name_to_con::Union{Dict{String,CI},Nothing} + # A useful dictionary for extensions to store things. These are + # _not_ copied between models! + ext::Dict{Symbol,Any} + function GenericOptimizer{T,C}() where {T,C} + return new{T,C}( + EMPTYSTRING, + false, + MOI.FEASIBILITY_SENSE, + false, + zero(MOI.ScalarAffineFunction{T}), + 0, + nothing, + UInt8[], + T[], + T[], + C(), + Dict{MOI.VariableIndex,String}(), + nothing, + Dict{MOI.ConstraintIndex,String}(), + nothing, + Dict{Symbol,Any}(), + ) end - return code end const LessThanIndicatorSetOne{T} = diff --git a/src/Utilities/struct_of_constraints.jl b/src/Utilities/struct_of_constraints.jl new file mode 100644 index 0000000000..70b0122a5c --- /dev/null +++ b/src/Utilities/struct_of_constraints.jl @@ -0,0 +1,266 @@ +abstract type StructOfConstraints <: MOI.ModelLike end + +function _throw_if_cannot_delete(model::StructOfConstraints, vis, fast_in_vis) + broadcastcall(model) do constrs + _throw_if_cannot_delete(constrs, vis, fast_in_vis) + end +end +function _deleted_constraints(callback::Function, model::StructOfConstraints, vi) + broadcastcall(model) do constrs + _deleted_constraints(callback, constrs, vi) + end +end + +function MOI.add_constraint(model::StructOfConstraints, func::MOI.AbstractFunction, set::MOI.AbstractSet) + if MOI.supports_constraint(model, typeof(func), typeof(set)) + return MOI.add_constraint(constraints(model, typeof(func), typeof(set)), func, set) + else + throw(MOI.UnsupportedConstraint{typeof(func),typeof(set)}()) + end +end + +function constraints( + model::StructOfConstraints, + ci::MOI.ConstraintIndex{F,S} +) where {F,S} + if !MOI.supports_constraint(model, F, S) + throw(MOI.InvalidIndex(ci)) + end + return constraints(model, F, S) +end +function MOI.get(model::StructOfConstraints, attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, ci::MOI.ConstraintIndex) + return MOI.get(constraints(model, ci), attr, ci) +end + +function MOI.delete(model::StructOfConstraints, ci::MOI.ConstraintIndex) + return MOI.delete(constraints(model, ci), ci) +end + +function MOI.is_valid( + model::StructOfConstraints, + ci::MOI.ConstraintIndex{F,S} +) where {F,S} + if MOI.supports_constraint(model, F, S) + return MOI.is_valid(constraints(model, ci), ci) + else + return false + end +end + +function MOI.modify( + model::StructOfConstraints, + ci::MOI.ConstraintIndex, + change::MOI.AbstractFunctionModification, +) + return MOI.modify(constraints(model, ci), ci, change) +end + +function MOI.set( + model::StructOfConstraints, + attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, + ci::MOI.ConstraintIndex, + func_or_set, +) + return MOI.set(constraints(model, ci), attr, ci, func_or_set) +end + + +function MOI.get(model::StructOfConstraints, loc::MOI.ListOfConstraints) where {T} + return broadcastvcat(model) do v + MOI.get(v, loc) + end +end + +function MOI.get(model::StructOfConstraints, noc::MOI.NumberOfConstraints{F,S}) where {F,S} + if MOI.supports_constraint(model, F, S) + return MOI.get(constraints(model, F, S), noc) + else + return 0 + end +end + +function MOI.get(model::StructOfConstraints, loc::MOI.ListOfConstraintIndices{F,S}) where {F,S} + if MOI.supports_constraint(model, F, S) + return MOI.get(constraints(model, F, S), loc) + else + return MOI.ConstraintIndex{F,S}[] + end +end + +function MOI.is_empty(model::StructOfConstraints) + return mapreduce_constraints(MOI.is_empty, &, model, true) +end +function MOI.empty!(model::StructOfConstraints) + broadcastcall(MOI.empty!, model) +end + +# Can be used to access constraints of a model +""" +broadcastcall(f::Function, model::AbstractModel) + +Calls `f(contrs)` for every vector `constrs::Vector{ConstraintIndex{F, S}, F, S}` of the model. + +# Examples + +To add all constraints of the model to a solver `solver`, one can do +```julia +_addcon(solver, ci, f, s) = MOI.add_constraint(solver, f, s) +function _addcon(solver, constrs::Vector) + for constr in constrs + _addcon(solver, constr...) + end +end +MOIU.broadcastcall(constrs -> _addcon(solver, constrs), model) +``` +""" +function broadcastcall end + +""" +broadcastvcat(f::Function, model::AbstractModel) + +Calls `f(contrs)` for every vector `constrs::Vector{ConstraintIndex{F, S}, F, S}` of the model and concatenate the results with `vcat` (this is used internally for `ListOfConstraints`). + +# Examples + +To get the list of all functions: +```julia +_getfun(ci, f, s) = f +_getfun(cindices::Tuple) = _getfun(cindices...) +_getfuns(constrs::Vector) = _getfun.(constrs) +MOIU.broadcastvcat(_getfuns, model) +``` +""" +function broadcastvcat end + +function mapreduce_constraints end + +# Macro code + +abstract type SymbolFS end +struct SymbolFun <: SymbolFS + s::Union{Symbol,Expr} + typed::Bool +end +struct SymbolSet <: SymbolFS + s::Union{Symbol,Expr} + typed::Bool +end + +# QuoteNode prevents s from being interpolated and keeps it as a symbol +# Expr(:., MOI, s) would be MOI.s +# Expr(:., MOI, $s) would be Expr(:., MOI, EqualTo) +# Expr(:., MOI, :($s)) would be Expr(:., MOI, :EqualTo) +# Expr(:., MOI, :($(QuoteNode(s)))) is Expr(:., MOI, :(:EqualTo)) <- what we want + +# (MOI, :Zeros) -> :(MOI.Zeros) +# (:Zeros) -> :(MOI.Zeros) +_set(s::SymbolSet) = esc(s.s) +_fun(s::SymbolFun) = esc(s.s) +function _typedset(s::SymbolSet) + if s.typed + :($(_set(s)){T}) + else + _set(s) + end +end +function _typedfun(s::SymbolFun) + if s.typed + :($(_fun(s)){T}) + else + _fun(s) + end +end + +# Base.lowercase is moved to Unicode.lowercase in Julia v0.7 +using Unicode + +_field(s::SymbolFS) = Symbol(replace(lowercase(string(s.s)), "." => "_")) + +_getC(s::SymbolSet) = :(VectorOfConstraints{F,$(_typedset(s))}) +_getC(s::SymbolFun) = _typedfun(s) + +_callfield(f, s::SymbolFS) = :($f(model.$(_field(s)))) +_broadcastfield(b, s::SymbolFS) = :($b(f, model.$(_field(s)))) +_mapreduce_field(s::SymbolFS) = :(cur = $MOIU.mapreduce_constraints(f, op, model.$(_field(s)), cur)) +_mapreduce_constraints(s::SymbolFS) = :(cur = op(cur, f(model.$(_field(s))))) + +function struct_of_constraint_code(struct_name, types) + field_types = [Symbol("C$i") for i in eachindex(types)] + esc_struct_name = struct_name + typed_struct = :($(esc_struct_name){T}) + append!(typed_struct.args, field_types) + + struct_def = :(struct $typed_struct <: StructOfConstraints + end) + + for (t, field_type) in zip(types, field_types) + field = _field(t) + push!(struct_def.args[3].args, :($field::$field_type)) + end + code = quote + function $MOIU.broadcastcall(f::Function, model::$esc_struct_name) + return $(Expr(:block, _callfield.(Ref(:f), types)...)) + end + function $MOIU.broadcastvcat(f::Function, model::$esc_struct_name) + return vcat($(_callfield.(Ref(:f), types)...)) + end + function $MOIU.mapreduce_constraints(f::Function, op::Function, model::$esc_struct_name, cur) + return $(Expr(:block, _mapreduce_field.(types)...)) + end + end + + for t in types + if t isa SymbolFun + fun = _fun(t) + set = :(MOI.AbstractSet) + else + fun = :(MOI.AbstractFunction) + set = _set(t) + end + field = _field(t) + code = quote + $code + function $MOIU.constraints( + model::$esc_struct_name, + ::Type{<:$fun}, + ::Type{<:$set}, + ) where S + return model.$field + end + end + end + supports_code = if eltype(types) <: SymbolFun + quote + function $MOI.supports_constraint( + model::$esc_struct_name{T}, + ::Type{F}, + ::Type{S}, + ) where {T, F<:Union{$(_typedfun.(types)...)}, S<:MOI.AbstractSet} + return $MOI.supports_constraint(constraints(model, F, S), F, S) + end + end + else + @assert eltype(types) <: SymbolSet + quote + function $MOI.supports_constraint( + model::$esc_struct_name{T}, + ::Type{F}, + ::Type{S}, + ) where {T, F<:MOI.AbstractFunction, S<:Union{$(_typedset.(types)...)}} + return $MOI.supports_constraint(constraints(model, F, S), F, S) + end + end + end + constructors = [:($field_type()) for field_type in field_types] + constructor_code = :(function $typed_struct() where {T} + return $typed_struct($(constructors...)) + end) + append!(constructor_code.args[1].args, field_types) + return Expr( + :block, + struct_def, + constructor_code, + supports_code, + code, + ) +end diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index 8c0f91d27f..eb4c252c86 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -15,7 +15,7 @@ mutable struct UniversalFallback{MT} <: MOI.ModelLike model::MT objective::Union{MOI.AbstractScalarFunction,Nothing} # See https://github.com/jump-dev/JuMP.jl/issues/1152 and https://github.com/jump-dev/JuMP.jl/issues/2238 for why we use an `OrderedDict` - single_variable_constraints::OrderedDict{Tuple{DataType,DataType},OrderedDict} + single_variable_constraints::OrderedDict{DataType,OrderedDict} constraints::OrderedDict{Tuple{DataType,DataType},VectorOfConstraints} con_to_name::Dict{CI,String} name_to_con::Union{Dict{String,MOI.ConstraintIndex},Nothing} @@ -28,7 +28,7 @@ mutable struct UniversalFallback{MT} <: MOI.ModelLike model, nothing, OrderedDict{Tuple{DataType,DataType},OrderedDict}(), - 0, + OrderedDict{Tuple{DataType,DataType},VectorOfConstraints}(), Dict{CI,String}(), nothing, Dict{MOI.AbstractOptimizerAttribute,Any}(), @@ -48,6 +48,7 @@ function Base.show(io::IO, U::UniversalFallback) MOIU.print_with_acronym(io, summary(U)) !(U.objective === nothing) && print(io, "\n$(indent)with objective") for (attr, name) in ( + (U.single_variable_constraints, "`SingleVariable` constraint"), (U.constraints, "constraint"), (U.optattr, "optimizer attribute"), (U.modattr, "model attribute"), @@ -91,22 +92,37 @@ function supports_default_copy_to(uf::UniversalFallback, copy_names::Bool) end # References -MOI.is_valid(uf::UniversalFallback, idx::VI) = MOI.is_valid(uf.model, idx) -function MOI.is_valid(uf::UniversalFallback, idx::CI{F,S}) where {F,S} - if MOI.supports_constraint(uf.model, F, S) - MOI.is_valid(uf.model, idx) +function MOI.is_valid(uf::UniversalFallback, idx::MOI.VariableIndex) + return MOI.is_valid(uf.model, idx) +end +function MOI.is_valid(uf::UniversalFallback, idx::CI{MOI.SingleVariable,S}) where {S} + if MOI.supports_constraint(uf.model, MOI.SingleVariable, S) + return MOI.is_valid(uf.model, idx) else - haskey(uf.constraints, (F, S)) && haskey(uf.constraints[(F, S)], idx) + return haskey(uf.single_variable_constraints, S) && + haskey(uf.single_variable_constraints[S], idx) end end -function MOI.delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{F,S}) where {F,S} - if MOI.supports_constraint(uf.model, F, S) +function MOI.is_valid(uf::UniversalFallback, idx::MOI.ConstraintIndex{F,S}) where {F,S} + if !MOI.supports_constraint(uf.model, F, S) && !haskey(uf.constraints, (F, S)) + return false + end + return MOI.is_valid(constraints(uf, idx), idx) +end +function _delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}) where {S} + if MOI.supports_constraint(uf.model, MOI.SingleVariable, S) MOI.delete(uf.model, ci) else - if !MOI.is_valid(uf, ci) - throw(MOI.InvalidIndex(ci)) - end - delete!(uf.constraints[(F, S)], ci) + MOI.is_valid(uf, ci) || throw(MOI.InvalidIndex(ci)) + delete!(uf.single_variable_constraints[S], ci) + end +end +function _delete(uf::UniversalFallback, ci::MOI.ConstraintIndex) + MOI.delete(constraints(uf, ci), ci) +end +function MOI.delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{F,S}) where {F,S} + _delete(uf, ci) + if !MOI.supports_constraint(uf.model, F, S) delete!(uf.con_to_name, ci) uf.name_to_con = nothing end @@ -117,69 +133,15 @@ end function _remove_variable( uf::UniversalFallback, constraints::OrderedDict{<:CI{MOI.SingleVariable}}, - vi::VI, -) - to_delete = keytype(constraints)[] - for (ci, constraint) in constraints - f::MOI.SingleVariable = constraint[1] - if f.variable == vi - push!(to_delete, ci) - end - end - return MOI.delete(uf, to_delete) -end -function _remove_variable( - uf::UniversalFallback, - constraints::OrderedDict{CI{MOI.VectorOfVariables,S}}, - vi::VI, -) where {S} - to_delete = keytype(constraints)[] - for (ci, constraint) in constraints - f::MOI.VectorOfVariables, s = constraint - if vi in f.variables - if length(f.variables) > 1 - if MOI.supports_dimension_update(S) - constraints[ci] = remove_variable(f, s, vi) - else - throw_delete_variable_in_vov(vi) - end - else - push!(to_delete, ci) - end - end - end - return MOI.delete(uf, to_delete) -end -function _remove_variable( - ::UniversalFallback, - constraints::OrderedDict{<:CI}, - vi::VI, + vi::MOI.VariableIndex, ) - for (ci, constraint) in constraints - f, s = constraint - constraints[ci] = remove_variable(f, s, vi) - end + return MOI.delete(uf, [ci for ci in keys(constraints) if ci.value == vi.value]) end -function _remove_vector_of_variables( - uf::UniversalFallback, - constraints::OrderedDict{<:CI{MOI.VectorOfVariables}}, - vis::Vector{VI}, -) - to_delete = keytype(constraints)[] - for (ci, constraint) in constraints - f::MOI.VectorOfVariables = constraint[1] - if vis == f.variables - push!(to_delete, ci) - end +function MOI.delete(uf::UniversalFallback, vi::MOI.VariableIndex) + vis = [vi] + for constraints in values(uf.constraints) + _throw_if_cannot_delete(constraints, vis, vis) end - return MOI.delete(uf, to_delete) -end -function _remove_vector_of_variables( - ::UniversalFallback, - ::OrderedDict{<:CI}, - ::Vector{VI}, -) end -function MOI.delete(uf::UniversalFallback, vi::VI) MOI.delete(uf.model, vi) for d in values(uf.varattr) delete!(d, vi) @@ -187,11 +149,24 @@ function MOI.delete(uf::UniversalFallback, vi::VI) if uf.objective !== nothing uf.objective = remove_variable(uf.objective, vi) end - for (_, constraints) in uf.constraints + for constraints in values(uf.single_variable_constraints) _remove_variable(uf, constraints, vi) end + for constraints in values(uf.constraints) + _deleted_constraints(constraints, vi) do ci + delete!(uf.con_to_name, ci) + uf.name_to_con = nothing + for d in values(uf.conattr) + delete!(d, ci) + end + end + end end -function MOI.delete(uf::UniversalFallback, vis::Vector{VI}) +function MOI.delete(uf::UniversalFallback, vis::Vector{MOI.VariableIndex}) + fast_in_vis = Set(vis) + for constraints in values(uf.constraints) + _throw_if_cannot_delete(constraints, vis, fast_in_vis) + end MOI.delete(uf.model, vis) for d in values(uf.varattr) for vi in vis @@ -201,12 +176,20 @@ function MOI.delete(uf::UniversalFallback, vis::Vector{VI}) 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 constraints in values(uf.single_variable_constraints) for vi in vis _remove_variable(uf, constraints, vi) end end + for constraints in values(uf.constraints) + _deleted_constraints(constraints, vis) do ci + delete!(uf.con_to_name, ci) + uf.name_to_con = nothing + for d in values(uf.conattr) + delete!(d, ci) + end + end + end end # Attributes @@ -236,12 +219,6 @@ function _get( ci::MOI.ConstraintIndex, ) return MOI.get_fallback(uf, attr, ci) - func = MOI.get(uf, MOI.ConstraintFunction(), ci) - if is_canonical(func) - return func - else - return canonical(func) - end end function MOI.get( uf::UniversalFallback, @@ -278,36 +255,55 @@ function MOI.get( end function MOI.get( uf::UniversalFallback, - attr::MOI.NumberOfConstraints{F,S}, -) where {F,S} + attr::MOI.NumberOfConstraints{MOI.SingleVariable,S}, +) where {S} + F = MOI.SingleVariable if MOI.supports_constraint(uf.model, F, S) return MOI.get(uf.model, attr) else return length(get( - uf.constraints, - (F, S), - OrderedDict{CI{F,S},Tuple{F,S}}(), + uf.single_variable_constraints, + S, + OrderedDict{CI{F,S},S}(), )) end end function MOI.get( uf::UniversalFallback, - listattr::MOI.ListOfConstraintIndices{F,S}, + attr::MOI.NumberOfConstraints{F,S}, ) where {F,S} + return MOI.get(constraints(uf, F, S), attr) +end +function MOI.get( + uf::UniversalFallback, + listattr::MOI.ListOfConstraintIndices{MOI.SingleVariable,S}, +) where {S} + F = MOI.SingleVariable if MOI.supports_constraint(uf.model, F, S) MOI.get(uf.model, listattr) else collect(keys(get( - uf.constraints, - (F, S), - OrderedDict{CI{F,S},Tuple{F,S}}(), + uf.single_variable_constraints, + S, + OrderedDict{CI{F,S},S}(), ))) end end +function MOI.get( + uf::UniversalFallback, + listattr::MOI.ListOfConstraintIndices{F,S}, +) where {F,S} + return MOI.get(constraints(uf, F, S), listattr) +end function MOI.get(uf::UniversalFallback, listattr::MOI.ListOfConstraints) list = MOI.get(uf.model, listattr) - for (FS, constraints) in uf.constraints + for (S, constraints) in uf.single_variable_constraints if !isempty(constraints) + push!(list, (MOI.SingleVariable, S)) + end + end + for (FS, constraints) in uf.constraints + if !MOI.is_empty(constraints) push!(list, FS) end end @@ -570,22 +566,23 @@ end # Constraints function MOI.supports_constraint( uf::UniversalFallback, - ::Type{F}, - ::Type{S}, -) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + ::Type{<:MOI.AbstractFunction}, + ::Type{<:MOI.AbstractSet}, +) return true end function constraints( uf::UniversalFallback, ::Type{F}, ::Type{S}, + getter::Function=get, ) where {F,S} if MOI.supports_constraint(uf.model, F, S) return uf.model else - return get!(uf.constraints, (F, S)) do - return MOI.VectorOfVariables{F,S}() - end::MOI.VectorOfVariables{F,S} + return getter(uf.constraints, (F, S)) do + return VectorOfConstraints{F,S}() + end::VectorOfConstraints{F,S} end end function constraints( @@ -600,21 +597,17 @@ end function MOI.add_constraint( uf::UniversalFallback, func::MOI.SingleVariable, - set::MOI.AbstractScalarSet, -) - S = typeof(set) + set::S, +) where S <: MOI.AbstractScalarSet if MOI.supports_constraint(uf.model, MOI.SingleVariable, S) return MOI.add_constraint(uf.model, func, set) else constraints = - get!(uf.single_variable_constraints, (F, S)) do - return OrderedDict{ - CI{F,S}, - Tuple{F,S}, - }() - end::OrderedDict{CI{F,S},Tuple{F,S}} + get!(uf.single_variable_constraints, S) do + return OrderedDict{CI{MOI.SingleVariable,S},S,}() + end::OrderedDict{CI{MOI.SingleVariable,S},S} ci = MOI.ConstraintIndex{MOI.SingleVariable,S}(func.variable.value) - constraints[ci] = (f, s) + constraints[ci] = set return ci end end @@ -624,7 +617,7 @@ function MOI.add_constraint( set::MOI.AbstractSet, ) return MOI.add_constraint( - constraints(uf, typeof(func), typeof(set)), + constraints(uf, typeof(func), typeof(set), get!), func, set, ) @@ -671,7 +664,7 @@ function MOI.get( MOI.get(uf.model, MOI.ConstraintSet(), ci) else MOI.throw_if_not_valid(uf, ci) - return uf.single_variable_constraints[(F, S)][ci][2] + return uf.single_variable_constraints[S][ci] end end @@ -693,8 +686,7 @@ function MOI.set( MOI.set(uf.model, MOI.ConstraintSet(), ci, set) else MOI.throw_if_not_valid(uf, ci) - (f, _) = uf.single_variable_constraints[(F, S)][ci] - uf.single_variable_constraints[(F, S)][ci] = (f, set) + uf.single_variable_constraints[S][ci] = set end end diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl index c11359fbcd..7b17bd1911 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -27,10 +27,17 @@ end MOI.is_empty(v::VectorOfConstraints) = isempty(v.constraints) MOI.empty!(v::VectorOfConstraints) = empty!(v.constraints) +function MOI.supports_constraint( + v::VectorOfConstraints{F,S}, + ::Type{F}, + ::Type{S}, +) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + return true +end function MOI.add_constraint( v::VectorOfConstraints{F,S}, func::F, - set::S + set::S, ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} # f needs to be copied, see #2 # We canonicalize the constraint so that solvers can avoid having to canonicalize @@ -101,6 +108,8 @@ function MOI.modify( return end +# Deletion of variables in vector of variables + function _remove_variable(v::VectorOfConstraints, vi::MOI.VariableIndex) CleverDicts.map_values!(v.constraints) do func_set remove_variable(func_set..., vi) @@ -112,9 +121,6 @@ function _filter_variables(keep::Function, v::VectorOfConstraints) end end -function _vector_of_variables_with(::VectorOfConstraints, ::Union{MOI.VariableIndex,Vector{MOI.VariableIndex}}) - return MOI.ConstraintIndex{MOI.VectorOfVariables}[] -end function throw_delete_variable_in_vov(vi::MOI.VariableIndex) message = string( "Cannot delete variable as it is constrained with other", @@ -122,36 +128,50 @@ function throw_delete_variable_in_vov(vi::MOI.VariableIndex) ) return throw(MOI.DeleteNotAllowed(vi, message)) end -function _vector_of_variables_with( - v::VectorOfConstraints{MOI.VectorOfVariables}, - vi::MOI.VariableIndex, -) - rm = MOI.ConstraintIndex{MOI.VectorOfVariables}[] - for (ci, fs) in v.constraints - f, s = fs - if vi in f.variables - if length(f.variables) > 1 - # If `supports_dimension_update(s)` then the variable will be - # removed in `_remove_variable`. - if !MOI.supports_dimension_update(typeof(s)) - throw_delete_variable_in_vov(vi) +function _throw_if_cannot_delete(::VectorOfConstraints, vis, fast_in_vis) + # Nothing to do as it's not `VectorOfVariables` constraints +end +function _throw_if_cannot_delete(v::VectorOfConstraints{MOI.VectorOfVariables,S}, vis, fast_in_vis) where S<:MOI.AbstractVectorSet + if !MOI.supports_dimension_update(S) + for fs in values(v.constraints) + f = fs[1]::MOI.VectorOfVariables + if length(f.variables) > 1 && f.variables != vis + for vi in f.variables + if vi in fast_in_vis + # If `supports_dimension_update(S)` then the variable + # will be removed in `_filter_variables`. + throw_delete_variable_in_vov(vi) + end end - else - push!(rm, ci) end end end - return rm -end -function _vector_of_variables_with( - v::VectorOfConstraints{MOI.VectorOfVariables}, - vis::Vector{MOI.VariableIndex}, -) - rm = MOI.ConstraintIndex{MOI.VectorOfVariables}[] - for (ci, fs) in v.constraints - if vis == fs[1].variables - push!(rm, ci) +end +function _delete_variables(::Function, ::VectorOfConstraints, ::Vector{MOI.VariableIndex}) + # Nothing to do as it's not `VectorOfVariables` constraints +end +function _delete_variables(callback::Function, v::VectorOfConstraints{MOI.VectorOfVariables,S}, vis::Vector{MOI.VariableIndex}) where {S<:MOI.AbstractVectorSet} + filter!(v.constraints) do p + f = p.second[1] + del = if length(f.variables) == 1 + first(f.variables) in vis + else + vis == f.variables + end + if del + callback(p.first) end + return !del end - return rm + return +end +function _deleted_constraints(callback::Function, v::VectorOfConstraints, vi::MOI.VariableIndex) + vis = [vi] + _delete_variables(callback, v, vis) + _remove_variable(v, vi) +end +function _deleted_constraints(callback::Function, v::VectorOfConstraints, vis::Vector{MOI.VariableIndex}) + removed = Set(vis) + _delete_variables(callback, v, vis) + _filter_variables(vi -> !(vi in removed), v) end diff --git a/test/Utilities/universalfallback.jl b/test/Utilities/universalfallback.jl index 12dd4c720d..a59449fd2e 100644 --- a/test/Utilities/universalfallback.jl +++ b/test/Utilities/universalfallback.jl @@ -110,6 +110,7 @@ end @testset "Optimizer Attribute" begin attr = UnknownOptimizerAttribute() listattr = MOI.ListOfOptimizerAttributesSet() + empty!(uf.optattr) test_optmodattrs(uf, model, attr, listattr) end @testset "Model Attribute" begin @@ -262,8 +263,9 @@ end # 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 - @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)] + for set in sets + @test MOI.get(uf, MOI.ListOfConstraintIndices{F, typeof(set)}()) == [MOI.ConstraintIndex{F, typeof(set)}(1), MOI.ConstraintIndex{F, typeof(set)}(2)] + end end end