From c67d4b2af62db4b8414e713aa27013f34c83296d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 19 Feb 2021 13:35:58 +0100 Subject: [PATCH 1/9] Refactor Model and UniversalFallback --- src/Utilities/CleverDicts.jl | 47 ++-- src/Utilities/Utilities.jl | 1 + src/Utilities/model.jl | 349 ++++++------------------- src/Utilities/vector_of_constraints.jl | 110 ++++++++ 4 files changed, 219 insertions(+), 288 deletions(-) create mode 100644 src/Utilities/vector_of_constraints.jl diff --git a/src/Utilities/CleverDicts.jl b/src/Utilities/CleverDicts.jl index c9baf9c71f..383ff52d0e 100644 --- a/src/Utilities/CleverDicts.jl +++ b/src/Utilities/CleverDicts.jl @@ -62,22 +62,6 @@ mutable struct CleverDict{K,V,F<:Function,I<:Function} <: AbstractDict{K,V} set::BitSet vector::Vector{V} dict::OrderedCollections.OrderedDict{K,V} - function CleverDict{K,V}(n::Integer = 0) where {K,V} - set = BitSet() - sizehint!(set, n) - vec = Vector{K}(undef, n) - inverse_hash = x -> index_to_key(K, x) - hash = key_to_index - return new{K,V,typeof(hash),typeof(inverse_hash)}( - 0, - hash, - inverse_hash, - true, - set, - vec, - OrderedCollections.OrderedDict{K,V}(), - ) - end function CleverDict{K,V}( hash::F, inverse_hash::I, @@ -97,6 +81,9 @@ mutable struct CleverDict{K,V,F<:Function,I<:Function} <: AbstractDict{K,V} ) end end +function CleverDict{K,V}(n::Integer = 0) where {K,V} + return CleverDict{K,V}(key_to_index, index_to_key, n) +end """ index_to_key(::Type{K}, index::Int) @@ -125,7 +112,7 @@ function add_item(c::CleverDict{K,V}, val::V)::K where {K,V} error("Keys were added out of order. `add_item` requires that keys are always added in order.") end # adding a key in order - key = c.inverse_hash(Int64(c.last_index + 1))::K + key = c.inverse_hash(K, Int64(c.last_index + 1))::K c[key] = val return key end @@ -151,6 +138,14 @@ function Base.haskey(c::CleverDict{K}, key::K) where {K} return _is_dense(c) ? c.hash(key)::Int64 in c.set : haskey(c.dict, key) end +function Base.keys(c::CleverDict{K}) where K + return if _is_dense(c) + LazyMap{K}(K, eachindex(c.vector)) + else + keys(c.dict) + end +end + function Base.get(c::CleverDict, key, default) if _is_dense(c) if !haskey(c, key) @@ -285,7 +280,7 @@ function Base.iterate( return nothing else el, i = itr - new_el = c.inverse_hash(Int64(el))::K => c.vector[el]::V + new_el = c.inverse_hash(K, Int64(el))::K => c.vector[el]::V @static if VERSION >= v"1.4.0" return new_el, State(i[2], i[1]) else @@ -319,7 +314,7 @@ function Base.iterate( return nothing else el, i = itr - new_el = c.inverse_hash(Int64(el))::K => c.vector[el]::V + new_el = c.inverse_hash(K, Int64(el))::K => c.vector[el]::V @static if VERSION >= v"1.4.0" return new_el, State(i[2], i[1]) else @@ -363,4 +358,18 @@ function Base.resize!(c::CleverDict{K,V}, n) where {K,V} return end +Base.values(d::CleverDict) = _is_dense(d) ? d.vector : values(d.dict) + +# TODO `map!(f, values(dict::AbstractDict))` requires Julia 1.2 or later, +# use `map_values` once we drop Julia 1.1 and earlier. +function map_values!(f::Function, d::CleverDict) + if _is_dense(d) + map!(f, d.vector, d.vector) + else + for (k, v) in d.dict + d.dict[k] = f(v) + end + end +end + end diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index 4868cc2589..d55c3ead86 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -57,6 +57,7 @@ include("copy.jl") include("results.jl") include("variables.jl") +include("vector_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 040b06750d..cf578d77b7 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -1,116 +1,10 @@ -## Storage of constraints -# -# All `F`-in-`S` constraints are stored in a vector of `ConstraintEntry{F, S}`. -# The index in this vector of a constraint of index -# `ci::MOI.ConstraintIndex{F, S}` is given by `model.constrmap[ci.value]`. The -# advantage of this representation is that it does not require any dictionary -# hence it never needs to compute a hash. -# -# It may seem redundant to store the constraint index `ci` as well as the -# function and sets in the tuple but it is used to efficiently implement the -# getter for `MOI.ListOfConstraintIndices{F, S}`. It is also used to implement -# `MOI.delete`. Indeed, when a constraint is deleted, it is removed from the -# vector hence the index in the vector of all the functions that were stored -# after must be decreased by one. As the constraint index is stored in the -# vector, it readily gives the entries of `model.constrmap` that need to be -# updated. -const ConstraintEntry{F,S} = Tuple{CI{F,S},F,S} - const EMPTYSTRING = "" -# Implementation of MOI for vector of constraint -function _add_constraint( - constrs::Vector{ConstraintEntry{F,S}}, - ci::CI, - f::F, - s::S, -) where {F,S} - push!(constrs, (ci, f, s)) - return length(constrs) -end - -function _delete(constrs::Vector, ci::CI, i::Int) - deleteat!(constrs, i) - @view constrs[i:end] # will need to shift it in constrmap -end - -_getindex(ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = ci -function _getindex(constrs::Vector, ci::CI, i::Int) - return _getindex(constrs[i]...) -end - -_getfun(ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = f -function _getfunction(constrs::Vector, ci::CI, i::Int) - if !(1 ≤ i ≤ length(constrs)) - throw(MOI.InvalidIndex(ci)) - end - @assert ci.value == constrs[i][1].value - return _getfun(constrs[i]...) -end - -_gets(ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = s -function _getset(constrs::Vector, ci::CI, i::Int) - if !(1 ≤ i ≤ length(constrs)) - throw(MOI.InvalidIndex(ci)) - end - @assert ci.value == constrs[i][1].value - return _gets(constrs[i]...) -end - -_modifyconstr(ci::CI{F,S}, f::F, s::S, change::F) where {F,S} = (ci, change, s) -_modifyconstr(ci::CI{F,S}, f::F, s::S, change::S) where {F,S} = (ci, f, change) -function _modifyconstr( - ci::CI{F,S}, - f::F, - s::S, - change::MOI.AbstractFunctionModification, -) where {F,S} - return (ci, modify_function(f, change), s) -end -function _modify( - constrs::Vector{ConstraintEntry{F,S}}, - ci::CI{F}, - i::Int, - change, -) where {F,S} - return constrs[i] = _modifyconstr(constrs[i]..., change) -end - -function _getnoc( - constrs::Vector{ConstraintEntry{F,S}}, - ::MOI.NumberOfConstraints{F,S}, -) where {F,S} - return length(constrs) -end -# Might be called when calling NumberOfConstraint with different coefficient type than the one supported -_getnoc(::Vector, ::MOI.NumberOfConstraints) = 0 - -function _getloc( - constrs::Vector{ConstraintEntry{F,S}}, -)::Vector{Tuple{DataType,DataType}} where {F,S} - return isempty(constrs) ? [] : [(F, S)] -end - -function _getlocr( - constrs::Vector{ConstraintEntry{F,S}}, - ::MOI.ListOfConstraintIndices{F,S}, -) where {F,S} - return map(constr -> constr[1], constrs) -end -function _getlocr( - constrs::Vector{<:ConstraintEntry}, - ::MOI.ListOfConstraintIndices{F,S}, -) where {F,S} - return CI{F,S}[] -end - # Implementation of MOI for AbstractModel abstract type AbstractModelLike{T} <: MOI.ModelLike end abstract type AbstractOptimizer{T} <: MOI.AbstractOptimizer end const AbstractModel{T} = Union{AbstractModelLike{T},AbstractOptimizer{T}} -getconstrloc(model::AbstractModel, ci::CI) = model.constrmap[ci.value] - # Variables function MOI.get(model::AbstractModel, ::MOI.NumberOfVariables)::Int64 if model.variable_indices === nothing @@ -152,22 +46,9 @@ function remove_variable(f::MOI.VectorOfVariables, s, vi::VI) end return g, t end -function _remove_variable(constrs::Vector{<:ConstraintEntry}, vi::VI) - for i in eachindex(constrs) - ci, f, s = constrs[i] - constrs[i] = (ci, remove_variable(f, s, vi)...) - end -end -function filter_variables(keep::F, f, s) where {F<:Function} - return filter_variables(keep, f), s -end - -function filter_variables( - keep::F, - f::MOI.VectorOfVariables, - s, -) where {F<:Function} +filter_variables(keep::F, f, s) where {F<:Function} = filter_variables(keep, f), s +function filter_variables(keep::F, f::MOI.VectorOfVariables, s) where {F<:Function} g = filter_variables(keep, f) if length(g.variables) != length(f.variables) t = MOI.update_dimension(s, length(g.variables)) @@ -176,58 +57,6 @@ function filter_variables( end return g, t end - -function _filter_variables( - keep::F, - constrs::Vector{<:ConstraintEntry}, -) where {F<:Function} - for i in eachindex(constrs) - ci, f, s = constrs[i] - constrs[i] = (ci, filter_variables(keep, f, s)...) - end -end -function _vector_of_variables_with(::Vector, ::Union{VI,MOI.Vector{VI}}) - return CI{MOI.VectorOfVariables}[] -end -function throw_delete_variable_in_vov(vi::VI) - message = string( - "Cannot delete variable as it is constrained with other", - " variables in a `MOI.VectorOfVariables`.", - ) - return throw(MOI.DeleteNotAllowed(vi, message)) -end -function _vector_of_variables_with( - constrs::Vector{<:ConstraintEntry{MOI.VectorOfVariables}}, - vi::VI, -) - rm = CI{MOI.VectorOfVariables}[] - for (ci, f, s) in constrs - 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) - end - else - push!(rm, ci) - end - end - end - return rm -end -function _vector_of_variables_with( - constrs::Vector{<:ConstraintEntry{MOI.VectorOfVariables}}, - vis::Vector{VI}, -) - rm = CI{MOI.VectorOfVariables}[] - for (ci, f, s) in constrs - if vis == f.variables - push!(rm, ci) - end - end - return rm -end function _delete_variable( model::AbstractModel{T}, vi::MOI.VariableIndex, @@ -323,18 +152,7 @@ function MOI.is_valid( ) end function MOI.is_valid(model::AbstractModel, ci::CI{F,S}) where {F,S} - if ci.value > length(model.constrmap) - false - else - loc = getconstrloc(model, ci) - if iszero(loc) # This means that it has been deleted - false - elseif loc > MOI.get(model, MOI.NumberOfConstraints{F,S}()) - false - else - ci == _getindex(model, ci, getconstrloc(model, ci)) - end - end + return MOI.is_valid(constraints(model, ci), ci) end function MOI.is_valid(model::AbstractModel, vi::VI) if model.variable_indices === nothing @@ -678,47 +496,40 @@ function MOI.add_constraint( return CI{MOI.SingleVariable,typeof(s)}(index) end -function MOI.add_constraint( +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, - f::F, - s::S, -) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + ci::MOI.ConstraintIndex{F,S} +) where {F,S} if MOI.supports_constraint(model, F, S) - # We give the index value `nextconstraintid + 1` to the new constraint. - # As the same counter is used for all pairs of F-in-S constraints, - # the index value is unique across all constraint types as mentioned in - # `@model`'s doc. - ci = CI{F,S}(model.nextconstraintid += 1) - # f needs to be copied, see #2 - # We canonicalize the constraint so that solvers can avoid having to canonicalize - # it most of the time (they can check if they need to with `is_canonical`. - # Note that the canonicalization is not guaranteed if for instance - # `modify` is called and adds a new term. - # See https://github.com/jump-dev/MathOptInterface.jl/pull/1118 - push!( - model.constrmap, - _add_constraint(model, ci, canonical(f), copy(s)), - ) - return ci - else - throw(MOI.UnsupportedConstraint{F,S}()) + throw(MOI.InvalidIndex(ci)) end + return constraints(model, F, S) +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) end function _delete_constraint( model::AbstractModel, - ci::CI{MOI.SingleVariable,S}, + ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, ) where {S} + MOI.throw_if_not_valid(model, ci) return model.single_variable_mask[ci.value] &= ~single_variable_flag(S) end -function _delete_constraint(model::AbstractModel, ci::CI) - for (ci_next, _, _) in _delete(model, ci, getconstrloc(model, ci)) - model.constrmap[ci_next.value] -= 1 - end - return model.constrmap[ci.value] = 0 +function _delete_constraint(model::AbstractModel, ci::MOI.ConstraintIndex) + return MOI.delete(constraints(model, ci), ci) end -function MOI.delete(model::AbstractModel, ci::CI) - MOI.throw_if_not_valid(model, ci) +function MOI.delete(model::AbstractModel, ci::MOI.ConstraintIndex) _delete_constraint(model, ci) model.name_to_con = nothing return delete!(model.con_to_name, ci) @@ -726,10 +537,10 @@ end function MOI.modify( model::AbstractModel, - ci::CI, + ci::MOI.ConstraintIndex, change::MOI.AbstractFunctionModification, ) - return _modify(model, ci, getconstrloc(model, ci), change) + return MOI.modify(constraints(model, ci), ci, change) end function MOI.set( @@ -742,11 +553,11 @@ function MOI.set( end function MOI.set( model::AbstractModel, - ::MOI.ConstraintFunction, - ci::CI, - change::MOI.AbstractFunction, -) - return _modify(model, ci, getconstrloc(model, ci), change) + attr::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{F}, + func::F, +) where {F,S} + return MOI.set(constraints(model, ci), attr, ci, func) end function MOI.set( model::AbstractModel, @@ -765,11 +576,11 @@ function MOI.set( end function MOI.set( model::AbstractModel, - ::MOI.ConstraintSet, - ci::CI, - change::MOI.AbstractSet, -) - return _modify(model, ci, getconstrloc(model, ci), change) + attr::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{F,S}, + set::S, +) where {F,S} + return MOI.set(constraints(model, ci), attr, ci, set) end function MOI.get( @@ -779,8 +590,12 @@ function MOI.get( flag = single_variable_flag(S) return count(mask -> !iszero(flag & mask), model.single_variable_mask) end -function MOI.get(model::AbstractModel, noc::MOI.NumberOfConstraints) - return _getnoc(model, noc) +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 end function _add_contraint_type( @@ -795,7 +610,9 @@ function _add_contraint_type( return end function MOI.get(model::AbstractModel{T}, loc::MOI.ListOfConstraints) where {T} - list = broadcastvcat(_getloc, model) + list = broadcastvcat(model) do v + MOI.get(v, loc) + end for S in ( MOI.EqualTo{T}, MOI.GreaterThan{T}, @@ -824,8 +641,13 @@ function MOI.get( end return list end -function MOI.get(model::AbstractModel, loc::MOI.ListOfConstraintIndices) - return broadcastvcat(constrs -> _getlocr(constrs, loc), model) + +function MOI.get(model::AbstractModel, loc::MOI.ListOfConstraintIndices{F,S}) where {F,S} + if MOI.supports_constraint(model, F, S) + return MOI.ConstraintIndex{F,S}[] + else + return MOI.get(constraints(model, loc), loc) + end end function MOI.get( @@ -1011,10 +833,10 @@ using Unicode _field(s::SymbolFS) = Symbol(replace(lowercase(string(s.s)), "." => "_")) -_getC(s::SymbolSet) = :(ConstraintEntry{F,$(_typedset(s))}) +_getC(s::SymbolSet) = :(VectorOfConstraints{F,$(_typedset(s))}) _getC(s::SymbolFun) = _typedfun(s) -_getCV(s::SymbolSet) = :($(_getC(s))[]) +_getCV(s::SymbolSet) = :($(_getC(s))()) _getCV(s::SymbolFun) = :($(s.cname){T,$(_getC(s))}()) _callfield(f, s::SymbolFS) = :($f(model.$(_field(s)))) @@ -1181,7 +1003,7 @@ macro model( ((scalarconstraints, scalar_sets), (vectorconstraints, vector_sets)) for s in sets field = _field(s) - push!(c.args[3].args, :($field::Vector{$(_getC(s))})) + push!(c.args[3].args, :($field::$(_getC(s)))) end end @@ -1269,44 +1091,33 @@ macro model( end end - for (funct, T) in ( - (:_add_constraint, CI), - (:_modify, CI), - (:_delete, CI), - (:_getindex, CI), - (:_getfunction, CI), - (:_getset, CI), - (:_getnoc, MOI.NumberOfConstraints), - ) - 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.$funct( - model::$c, - ci::$T{F,<:$set}, - args..., - ) where {F} - return $funct(model.$field, ci, args...) - 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 $MOIU.constraints(model.$field, ci, args...) end end end + end - for f in funs - fun = _fun(f) - field = _field(f) - code = quote - $code - function $MOIU.$funct( - model::$esc_model_name, - ci::$T{<:$fun}, - args..., - ) - return $funct(model.$field, ci, args...) - 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 end diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl new file mode 100644 index 0000000000..5dc13e5a5e --- /dev/null +++ b/src/Utilities/vector_of_constraints.jl @@ -0,0 +1,110 @@ +## Storage of constraints +# +# All `F`-in-`S` constraints are stored in a vector of `ConstraintEntry{F, S}`. +# The index in this vector of a constraint of index +# `ci::MOI.ConstraintIndex{F, S}` is given by `model.constrmap[ci.value]`. The +# advantage of this representation is that it does not require any dictionary +# hence it never needs to compute a hash. +# +# It may seem redundant to store the constraint index `ci` as well as the +# function and sets in the tuple but it is used to efficiently implement the +# getter for `MOI.ListOfConstraintIndices{F, S}`. It is also used to implement +# `MOI.delete`. Indeed, when a constraint is deleted, it is removed from the +# vector hence the index in the vector of all the functions that were stored +# after must be decreased by one. As the constraint index is stored in the +# vector, it readily gives the entries of `model.constrmap` that need to be +# updated. + +struct VectorOfConstraints{F,S} <: MOI.ModelLike + constraints::CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S},typeof(CleverDicts.key_to_index),typeof(CleverDicts.index_to_key)} +end + +function MOI.add_constraint(v::VectorOfConstraints{F,S}, func::F, set::S) where {F,S} + return CleverDicts.add_item(v.constraints, (F, S)) +end +function MOI.delete(v::VectorOfConstraints{F,S}, ci::MOI.ConstraintIndex{F,S}) where {F,S} + MOI.throw_if_not_valid(v, ci) + delete!(v.constraints, ci) +end +function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.ConstraintFunction, ci::MOI.ConstraintIndex{F,S}) where {F,S} + return v.constraints[ci][1] +end +function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.ConstraintSet, ci::MOI.ConstraintIndex{F,S}) where {F,S} + return v.constraints[ci][2] +end + +function MOI.get( + v::VectorOfConstraints{F,S}, + ::MOI.ListOfConstraints +)::Vector{Tuple{DataType,DataType}} where {F,S} + return isempty(v.constraints) ? [] : [(F, S)] +end +function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.NumberOfConstraints{F,S}) where {F,S} + return length(v.constraints) +end +function MOI.get(v::VectorOfConstraints{F,S}, ci::MOI.ListOfConstraintIndices{F,S}) where {F,S} + return keys(v.constraints) +end +function MOI.modify( + v::VectorOfConstraints{F,S}, + ci::MOI.ConstraintIndex{F,S}, + change::MOI.AbstractFunctionModification, +) where {F,S} + func, set = constraint[ci] + constraint[ci] = (modify_function(func, change), set) + return +end + +function _remove_variable(v::VectorOfConstraints, vi::MOI.VariableIndex) + map_values(v.constraints) do func_set + remove_variable(func_set..., vi) + end +end +function _filter_variables(keep::Function, v::VectorOfConstraints) + map_values(v.constraints) do func_set + filter_variables(kepp, func_set...) + 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", + " variables in a `MOI.VectorOfVariables`.", + ) + 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 (f, s) in values(v) + 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) + 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 (f, s) in values(v) + if vis == f.variables + push!(rm, ci) + end + end + return rm +end From 818a8bcb3ceb914f7bf4063eda392084f527de13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 20 Feb 2021 09:46:40 +0100 Subject: [PATCH 2/9] Fixes --- src/Utilities/CleverDicts.jl | 10 +++++--- src/Utilities/model.jl | 6 ++--- src/Utilities/vector_of_constraints.jl | 35 +++++++++++++++++++------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Utilities/CleverDicts.jl b/src/Utilities/CleverDicts.jl index 383ff52d0e..5bfde525f5 100644 --- a/src/Utilities/CleverDicts.jl +++ b/src/Utilities/CleverDicts.jl @@ -10,7 +10,11 @@ function index_to_key(::Type{MathOptInterface.VariableIndex}, index::Int64) return MathOptInterface.VariableIndex(index) end -key_to_index(key::MathOptInterface.VariableIndex) = key.value +function index_to_key(::Type{MathOptInterface.ConstraintIndex{F,S}}, index::Int64) where {F,S} + return MathOptInterface.ConstraintIndex{F,S}(index) +end + +key_to_index(key::MathOptInterface.Index) = key.value # Now, on with `CleverDicts`. @@ -138,9 +142,9 @@ function Base.haskey(c::CleverDict{K}, key::K) where {K} return _is_dense(c) ? c.hash(key)::Int64 in c.set : haskey(c.dict, key) end -function Base.keys(c::CleverDict{K}) where K +function Base.keys(c::CleverDict) return if _is_dense(c) - LazyMap{K}(K, eachindex(c.vector)) + map(c.inverse_hash, c.set) else keys(c.dict) end diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index cf578d77b7..6c6c78d6ac 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -507,7 +507,7 @@ function constraints( model::AbstractModel, ci::MOI.ConstraintIndex{F,S} ) where {F,S} - if MOI.supports_constraint(model, F, S) + if !MOI.supports_constraint(model, F, S) throw(MOI.InvalidIndex(ci)) end return constraints(model, F, S) @@ -1086,7 +1086,7 @@ macro model( return vcat($(_callfield.(:f, sets)...)) end function $MOI.empty!(model::$cname) - return $(Expr(:block, _callfield.(Ref(:(Base.empty!)), sets)...)) + return $(Expr(:block, _callfield.(Ref(:($MOI.empty!)), sets)...)) end end end @@ -1101,7 +1101,7 @@ macro model( model::$c, ::Type{<:$set}, ) where {F} - return $MOIU.constraints(model.$field, ci, args...) + return model.$field end end end diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl index 5dc13e5a5e..a5eebae078 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -15,12 +15,29 @@ # vector, it readily gives the entries of `model.constrmap` that need to be # updated. -struct VectorOfConstraints{F,S} <: MOI.ModelLike +struct VectorOfConstraints{F<:MOI.AbstractFunction,S<:MOI.AbstractSet} <: MOI.ModelLike constraints::CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S},typeof(CleverDicts.key_to_index),typeof(CleverDicts.index_to_key)} + function VectorOfConstraints{F,S}() where {F,S} + return new{F,S}(CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S}}()) + end +end + +function MOI.empty!(v::VectorOfConstraints) + empty!(v.constraints) end -function MOI.add_constraint(v::VectorOfConstraints{F,S}, func::F, set::S) where {F,S} - return CleverDicts.add_item(v.constraints, (F, S)) +function MOI.add_constraint( + v::VectorOfConstraints{F,S}, + func::F, + set::S +) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + return CleverDicts.add_item(v.constraints, (func, set)) +end +function MOI.is_valid( + v::VectorOfConstraints{F,S}, + ci::MOI.ConstraintIndex{F,S} +) where {F,S} + return haskey(v.constraints, ci) end function MOI.delete(v::VectorOfConstraints{F,S}, ci::MOI.ConstraintIndex{F,S}) where {F,S} MOI.throw_if_not_valid(v, ci) @@ -56,13 +73,13 @@ function MOI.modify( end function _remove_variable(v::VectorOfConstraints, vi::MOI.VariableIndex) - map_values(v.constraints) do func_set + CleverDicts.map_values!(v.constraints) do func_set remove_variable(func_set..., vi) end end function _filter_variables(keep::Function, v::VectorOfConstraints) - map_values(v.constraints) do func_set - filter_variables(kepp, func_set...) + CleverDicts.map_values!(v.constraints) do func_set + filter_variables(keep, func_set...) end end @@ -81,7 +98,7 @@ function _vector_of_variables_with( vi::MOI.VariableIndex, ) rm = MOI.ConstraintIndex{MOI.VectorOfVariables}[] - for (f, s) in values(v) + for (f, s) in values(v.constraints) if vi in f.variables if length(f.variables) > 1 # If `supports_dimension_update(s)` then the variable will be @@ -101,8 +118,8 @@ function _vector_of_variables_with( vis::Vector{MOI.VariableIndex}, ) rm = MOI.ConstraintIndex{MOI.VectorOfVariables}[] - for (f, s) in values(v) - if vis == f.variables + for (ci, fs) in v.constraints + if vis == fs[1].variables push!(rm, ci) end end From 076295be8d29ddef8b613a0b7a68ce76a89646c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 22 Feb 2021 22:47:18 +0100 Subject: [PATCH 3/9] Fixes --- src/Utilities/CleverDicts.jl | 6 ++--- src/Utilities/model.jl | 21 ++++++++++------ src/Utilities/vector_of_constraints.jl | 34 +++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/Utilities/CleverDicts.jl b/src/Utilities/CleverDicts.jl index 5bfde525f5..13cc59bd3c 100644 --- a/src/Utilities/CleverDicts.jl +++ b/src/Utilities/CleverDicts.jl @@ -142,11 +142,11 @@ function Base.haskey(c::CleverDict{K}, key::K) where {K} return _is_dense(c) ? c.hash(key)::Int64 in c.set : haskey(c.dict, key) end -function Base.keys(c::CleverDict) +function Base.keys(c::CleverDict{K}) where {K} return if _is_dense(c) - map(c.inverse_hash, c.set) + [c.inverse_hash(K, index) for index in c.set] else - keys(c.dict) + collect(keys(c.dict)) end end diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 6c6c78d6ac..f6548158a2 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -152,7 +152,11 @@ function MOI.is_valid( ) end function MOI.is_valid(model::AbstractModel, ci::CI{F,S}) where {F,S} - return MOI.is_valid(constraints(model, ci), ci) + if MOI.supports_constraint(model, F, S) + return MOI.is_valid(constraints(model, ci), ci) + else + return false + end end function MOI.is_valid(model::AbstractModel, vi::VI) if model.variable_indices === nothing @@ -644,9 +648,9 @@ end function MOI.get(model::AbstractModel, loc::MOI.ListOfConstraintIndices{F,S}) where {F,S} if MOI.supports_constraint(model, F, S) - return MOI.ConstraintIndex{F,S}[] + return MOI.get(constraints(model, F, S), loc) else - return MOI.get(constraints(model, loc), loc) + return MOI.ConstraintIndex{F,S}[] end end @@ -658,8 +662,12 @@ function MOI.get( MOI.throw_if_not_valid(model, ci) return MOI.SingleVariable(MOI.VariableIndex(ci.value)) end -function MOI.get(model::AbstractModel, ::MOI.ConstraintFunction, ci::CI) - return _getfunction(model, ci, getconstrloc(model, ci)) +function MOI.get( + model::AbstractModel, + attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, + ci::MOI.ConstraintIndex +) + return MOI.get(constraints(model, ci), attr, ci) end function _get_single_variable_set( @@ -706,9 +714,6 @@ function MOI.get( MOI.throw_if_not_valid(model, ci) return _get_single_variable_set(model, S, ci.value) end -function MOI.get(model::AbstractModel, ::MOI.ConstraintSet, ci::CI) - return _getset(model, ci, getconstrloc(model, ci)) -end function MOI.is_empty(model::AbstractModel) return isempty(model.name) && diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl index a5eebae078..ac4554d206 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -31,7 +31,13 @@ function MOI.add_constraint( func::F, set::S ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - return CleverDicts.add_item(v.constraints, (func, set)) + # f needs to be copied, see #2 + # We canonicalize the constraint so that solvers can avoid having to canonicalize + # it most of the time (they can check if they need to with `is_canonical`. + # Note that the canonicalization is not guaranteed if for instance + # `modify` is called and adds a new term. + # See https://github.com/jump-dev/MathOptInterface.jl/pull/1118 + return CleverDicts.add_item(v.constraints, (canonical(func), copy(set))) end function MOI.is_valid( v::VectorOfConstraints{F,S}, @@ -44,11 +50,33 @@ function MOI.delete(v::VectorOfConstraints{F,S}, ci::MOI.ConstraintIndex{F,S}) w delete!(v.constraints, ci) end function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.ConstraintFunction, ci::MOI.ConstraintIndex{F,S}) where {F,S} + MOI.throw_if_not_valid(v, ci) return v.constraints[ci][1] end function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.ConstraintSet, ci::MOI.ConstraintIndex{F,S}) where {F,S} + MOI.throw_if_not_valid(v, ci) return v.constraints[ci][2] end +function MOI.set( + v::VectorOfConstraints{F,S}, + ::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{F,S}, + func::F, +) where {F,S} + MOI.throw_if_not_valid(v, ci) + v.constraints[ci] = (func, v.constraints[ci][2]) + return +end +function MOI.set( + v::VectorOfConstraints{F,S}, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{F,S}, + set::S, +) where {F,S} + MOI.throw_if_not_valid(v, ci) + v.constraints[ci] = (v.constraints[ci][1], set) + return +end function MOI.get( v::VectorOfConstraints{F,S}, @@ -67,8 +95,8 @@ function MOI.modify( ci::MOI.ConstraintIndex{F,S}, change::MOI.AbstractFunctionModification, ) where {F,S} - func, set = constraint[ci] - constraint[ci] = (modify_function(func, change), set) + func, set = v.constraints[ci] + v.constraints[ci] = (modify_function(func, change), set) return end From 5edde4cceb2c2f0618637e2f913836f30b9b7a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 22 Feb 2021 23:29:11 +0100 Subject: [PATCH 4/9] Simplify --- src/Utilities/model.jl | 54 +++++++++++++------------- src/Utilities/vector_of_constraints.jl | 8 ++-- test/Utilities/model.jl | 4 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index f6548158a2..e40bbbf481 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -722,9 +722,27 @@ function MOI.is_empty(model::AbstractModel) isempty(model.objective.terms) && iszero(model.objective.constant) && iszero(model.num_variables_created) && - iszero(model.nextconstraintid) + mapreduce_constraints(MOI.is_empty, &, model, true) +end +function MOI.empty!(model::AbstractModel{T}) where {T} + model.name = "" + model.senseset = false + model.sense = MOI.FEASIBILITY_SENSE + model.objectiveset = false + model.objective = zero(MOI.ScalarAffineFunction{T}) + model.num_variables_created = 0 + model.variable_indices = nothing + model.single_variable_mask = UInt8[] + model.lower_bound = T[] + model.upper_bound = T[] + empty!(model.var_to_name) + model.name_to_var = nothing + empty!(model.con_to_name) + model.name_to_con = nothing + broadcastcall(MOI.empty!, model) end + function MOI.copy_to(dest::AbstractModel, src::MOI.ModelLike; kws...) return automatic_copy_to(dest, src; kws...) end @@ -794,6 +812,8 @@ MOIU.broadcastvcat(_getfuns, model) """ function broadcastvcat end +function mapreduce_constraints end + # Macro to generate Model abstract type Constraints{F} end @@ -846,6 +866,8 @@ _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. @@ -951,10 +973,8 @@ mutable struct LPModel{T} <: MOIU.AbstractModel{T} 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} - nextconstraintid::Int64 con_to_name::Dict{MOI.ConstraintIndex, String} name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} - constrmap::Vector{Int} scalaraffinefunction::LPModelScalarConstraints{T, MOI.ScalarAffineFunction{T}} vectorofvariables::LPModelVectorConstraints{T, MOI.VectorOfVariables} vectoraffinefunction::LPModelVectorConstraints{T, MOI.VectorAffineFunction{T}} @@ -1039,10 +1059,8 @@ macro model( var_to_name::Dict{$VI,String} # If `nothing`, the dictionary hasn't been constructed yet. name_to_var::Union{Dict{String,$VI},Nothing} - nextconstraintid::Int64 con_to_name::Dict{$CI,String} name_to_con::Union{Dict{String,$CI},Nothing} - constrmap::Vector{Int} # Constraint Reference value ci -> index in array in Constraints # A useful dictionary for extensions to store things. These are # _not_ copied between models! ext::Dict{Symbol,Any} @@ -1061,24 +1079,8 @@ macro model( function $MOIU.broadcastvcat(f::F, model::$esc_model_name) where {F<:Function} return vcat($(_broadcastfield.(Ref(:(broadcastvcat)), funs)...)) end - function $MOI.empty!(model::$esc_model_name{T}) where {T} - model.name = "" - model.senseset = false - model.sense = $MOI.FEASIBILITY_SENSE - model.objectiveset = false - model.objective = zero($MOI.ScalarAffineFunction{T}) - model.num_variables_created = 0 - model.variable_indices = nothing - model.single_variable_mask = UInt8[] - model.lower_bound = T[] - model.upper_bound = T[] - empty!(model.var_to_name) - model.name_to_var = nothing - model.nextconstraintid = 0 - empty!(model.con_to_name) - model.name_to_con = nothing - empty!(model.constrmap) - return $(Expr(:block, _callfield.(Ref(:($MOI.empty!)), funs)...)) + function $MOIU.mapreduce_constraints(f::Function, op::Function, model::$esc_model_name, cur) + return $(Expr(:block, _mapreduce_field.(funs)...)) end end for (cname, sets) in ((scname, scalar_sets), (vcname, vector_sets)) @@ -1090,8 +1092,8 @@ macro model( function $MOIU.broadcastvcat(f::F, model::$cname) where {F<:Function} return vcat($(_callfield.(:f, sets)...)) end - function $MOI.empty!(model::$cname) - return $(Expr(:block, _callfield.(Ref(:($MOI.empty!)), sets)...)) + function $MOIU.mapreduce_constraints(f::Function, op::Function, model::$cname, cur) + return $(Expr(:block, _mapreduce_constraints.(sets)...)) end end end @@ -1153,10 +1155,8 @@ macro model( T[], Dict{$VI,String}(), nothing, - 0, Dict{$CI,String}(), nothing, - Int[], Dict{Symbol,Any}(), $(_getCV.(funs)...), ) diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl index ac4554d206..1bae7303e2 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -22,9 +22,8 @@ struct VectorOfConstraints{F<:MOI.AbstractFunction,S<:MOI.AbstractSet} <: MOI.Mo end end -function MOI.empty!(v::VectorOfConstraints) - empty!(v.constraints) -end +MOI.is_empty(v::VectorOfConstraints) = isempty(v.constraints) +MOI.empty!(v::VectorOfConstraints) = empty!(v.constraints) function MOI.add_constraint( v::VectorOfConstraints{F,S}, @@ -126,7 +125,8 @@ function _vector_of_variables_with( vi::MOI.VariableIndex, ) rm = MOI.ConstraintIndex{MOI.VectorOfVariables}[] - for (f, s) in values(v.constraints) + 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 diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index fa7c24ed80..18fa95cb85 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -219,8 +219,8 @@ end loc1 = MOI.get(model, MOI.ListOfConstraints()) loc2 = Vector{Tuple{DataType, DataType}}() - function _pushloc(constrs::Vector{MOIU.ConstraintEntry{F, S}}) where {F, S} - if !isempty(constrs) + function _pushloc(v::MOI.Utilities.VectorOfConstraints{F, S}) where {F, S} + if !MOI.is_empty(v) push!(loc2, (F, S)) end end From b6d01f854b9791e1e4ad3ee1acaeb6d9fbc69d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 23 Feb 2021 16:57:56 +0100 Subject: [PATCH 5/9] Don't do breaking changes to CleverDicts --- src/Utilities/CleverDicts.jl | 10 ++++----- src/Utilities/model.jl | 28 +++++++++++++------------- src/Utilities/vector_of_constraints.jl | 4 +++- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/Utilities/CleverDicts.jl b/src/Utilities/CleverDicts.jl index 13cc59bd3c..47683d9def 100644 --- a/src/Utilities/CleverDicts.jl +++ b/src/Utilities/CleverDicts.jl @@ -86,7 +86,7 @@ mutable struct CleverDict{K,V,F<:Function,I<:Function} <: AbstractDict{K,V} end end function CleverDict{K,V}(n::Integer = 0) where {K,V} - return CleverDict{K,V}(key_to_index, index_to_key, n) + return CleverDict{K,V}(key_to_index, Base.Fix1(index_to_key, K), n) end """ @@ -116,7 +116,7 @@ function add_item(c::CleverDict{K,V}, val::V)::K where {K,V} error("Keys were added out of order. `add_item` requires that keys are always added in order.") end # adding a key in order - key = c.inverse_hash(K, Int64(c.last_index + 1))::K + key = c.inverse_hash(Int64(c.last_index + 1))::K c[key] = val return key end @@ -144,7 +144,7 @@ end function Base.keys(c::CleverDict{K}) where {K} return if _is_dense(c) - [c.inverse_hash(K, index) for index in c.set] + [c.inverse_hash(Int64(index))::K for index in c.set] else collect(keys(c.dict)) end @@ -284,7 +284,7 @@ function Base.iterate( return nothing else el, i = itr - new_el = c.inverse_hash(K, Int64(el))::K => c.vector[el]::V + new_el = c.inverse_hash(Int64(el))::K => c.vector[el]::V @static if VERSION >= v"1.4.0" return new_el, State(i[2], i[1]) else @@ -318,7 +318,7 @@ function Base.iterate( return nothing else el, i = itr - new_el = c.inverse_hash(K, Int64(el))::K => c.vector[el]::V + new_el = c.inverse_hash(Int64(el))::K => c.vector[el]::V @static if VERSION >= v"1.4.0" return new_el, State(i[2], i[1]) else diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index e40bbbf481..58ebce88d7 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -548,34 +548,34 @@ function MOI.modify( end function MOI.set( - model::AbstractModel, + ::AbstractModel, ::MOI.ConstraintFunction, - ci::CI{MOI.SingleVariable}, - change::MOI.AbstractFunction, + ::CI{MOI.SingleVariable}, + ::MOI.SingleVariable, ) return throw(MOI.SettingSingleVariableFunctionNotAllowed()) end function MOI.set( model::AbstractModel, - attr::MOI.ConstraintFunction, - ci::MOI.ConstraintIndex{F}, - func::F, -) where {F,S} - return MOI.set(constraints(model, ci), attr, ci, func) + 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, + model::AbstractModel{T}, ::MOI.ConstraintSet, ci::CI{MOI.SingleVariable}, - change::MOI.AbstractSet, -) + set::SUPPORTED_VARIABLE_SCALAR_SETS{T}, +) where {T} MOI.throw_if_not_valid(model, ci) - flag = single_variable_flag(typeof(change)) + flag = single_variable_flag(typeof(set)) if !iszero(flag & LOWER_BOUND_MASK) - model.lower_bound[ci.value] = extract_lower_bound(change) + model.lower_bound[ci.value] = extract_lower_bound(set) end if !iszero(flag & UPPER_BOUND_MASK) - model.upper_bound[ci.value] = extract_upper_bound(change) + model.upper_bound[ci.value] = extract_upper_bound(set) end end function MOI.set( diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl index 1bae7303e2..c11359fbcd 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -16,7 +16,9 @@ # updated. struct VectorOfConstraints{F<:MOI.AbstractFunction,S<:MOI.AbstractSet} <: MOI.ModelLike - constraints::CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S},typeof(CleverDicts.key_to_index),typeof(CleverDicts.index_to_key)} + # FIXME It is not ideal that we have `DataType` here, it might induce type instabilities. + # We should change `CleverDicts` so that we can just use `typeof(CleverDicts.index_to_key)` here. + constraints::CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S},typeof(CleverDicts.key_to_index),Base.Fix1{typeof(CleverDicts.index_to_key),DataType}} function VectorOfConstraints{F,S}() where {F,S} return new{F,S}(CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S}}()) end From a76f8682486ab152afaa761597d4c7ed472aaa84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 23 Feb 2021 21:44:30 +0100 Subject: [PATCH 6/9] Refactor UniversalFallback --- src/Utilities/universalfallback.jl | 149 +++++++++++++++++------------ 1 file changed, 86 insertions(+), 63 deletions(-) diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index 678097a038..8c0f91d27f 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -14,8 +14,9 @@ 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/jump-dev/JuMP.jl/issues/1152 and https://github.com/jump-dev/JuMP.jl/issues/2238 - nextconstraintid::Int64 + # 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} + constraints::OrderedDict{Tuple{DataType,DataType},VectorOfConstraints} con_to_name::Dict{CI,String} name_to_con::Union{Dict{String,MOI.ConstraintIndex},Nothing} optattr::Dict{MOI.AbstractOptimizerAttribute,Any} @@ -65,6 +66,7 @@ end function MOI.is_empty(uf::UniversalFallback) return MOI.is_empty(uf.model) && uf.objective === nothing && + isempty(uf.single_variable_constraints) && isempty(uf.constraints) && isempty(uf.modattr) && isempty(uf.varattr) && @@ -73,8 +75,8 @@ end function MOI.empty!(uf::UniversalFallback) MOI.empty!(uf.model) uf.objective = nothing + empty!(uf.single_variable_constraints) empty!(uf.constraints) - uf.nextconstraintid = 0 empty!(uf.con_to_name) uf.name_to_con = nothing empty!(uf.modattr) @@ -97,7 +99,7 @@ function MOI.is_valid(uf::UniversalFallback, idx::CI{F,S}) where {F,S} haskey(uf.constraints, (F, S)) && haskey(uf.constraints[(F, S)], idx) end end -function MOI.delete(uf::UniversalFallback, ci::CI{F,S}) where {F,S} +function MOI.delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{F,S}) where {F,S} if MOI.supports_constraint(uf.model, F, S) MOI.delete(uf.model, ci) else @@ -573,105 +575,126 @@ function MOI.supports_constraint( ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} return true end -function _new_constraint_index( - uf, - f::MOI.SingleVariable, - s::MOI.AbstractScalarSet, -) - return CI{MOI.SingleVariable,typeof(s)}(f.variable.value) +function constraints( + uf::UniversalFallback, + ::Type{F}, + ::Type{S}, +) 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} + end end -function _new_constraint_index(uf, f::MOI.AbstractFunction, s::MOI.AbstractSet) - uf.nextconstraintid += 1 - return CI{typeof(f),typeof(s)}(uf.nextconstraintid) +function constraints( + uf::UniversalFallback, + ci::MOI.ConstraintIndex{F,S} +) where {F,S} + if !MOI.supports_constraint(uf, F, S) + throw(MOI.InvalidIndex(ci)) + end + return constraints(uf, F, S) end function MOI.add_constraint( uf::UniversalFallback, - f::MOI.AbstractFunction, - s::MOI.AbstractSet, + func::MOI.SingleVariable, + set::MOI.AbstractScalarSet, ) - F = typeof(f) - S = typeof(s) - if MOI.supports_constraint(uf.model, F, S) - return MOI.add_constraint(uf.model, f, s) + S = typeof(set) + if MOI.supports_constraint(uf.model, MOI.SingleVariable, S) + return MOI.add_constraint(uf.model, func, set) else constraints = - get!(uf.constraints, (F, S)) do + get!(uf.single_variable_constraints, (F, S)) do return OrderedDict{ CI{F,S}, Tuple{F,S}, }() end::OrderedDict{CI{F,S},Tuple{F,S}} - ci = _new_constraint_index(uf, canonical(f), copy(s)) + ci = MOI.ConstraintIndex{MOI.SingleVariable,S}(func.variable.value) constraints[ci] = (f, s) return ci end end +function MOI.add_constraint( + uf::UniversalFallback, + func::MOI.AbstractFunction, + set::MOI.AbstractSet, +) + return MOI.add_constraint( + constraints(uf, typeof(func), typeof(set)), + func, + set, + ) +end function MOI.modify( uf::UniversalFallback, - ci::CI{F,S}, + ci::MOI.ConstraintIndex, change::MOI.AbstractFunctionModification, -) where {F,S} - if MOI.supports_constraint(uf.model, F, S) - MOI.modify(uf.model, ci, change) - else - (f, s) = uf.constraints[(F, S)][ci] - uf.constraints[(F, S)][ci] = (modify_function(f, change), s) - end +) + MOI.modify(constraints(uf, ci), ci, change) end function MOI.get( uf::UniversalFallback, - attr::MOI.ConstraintFunction, - ci::CI{F,S}, -) where {F,S} - if MOI.supports_constraint(uf.model, F, S) - MOI.get(uf.model, attr, ci) - else - MOI.throw_if_not_valid(uf, ci) - uf.constraints[(F, S)][ci][1] - end + attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, + ci::MOI.ConstraintIndex, +) + return MOI.get(constraints(uf, ci), attr, ci) end + +function MOI.set( + uf::UniversalFallback, + attr::Union{MOI.ConstraintFunction, MOI.ConstraintSet}, + ci::MOI.ConstraintIndex, + func_or_set, +) + return MOI.set(constraints(uf, ci), attr, ci, func_or_set) +end + function MOI.get( uf::UniversalFallback, - attr::MOI.ConstraintSet, - ci::CI{F,S}, -) where {F,S} - if MOI.supports_constraint(uf.model, F, S) - MOI.get(uf.model, attr, ci) + ::MOI.ConstraintFunction, + ci::CI{MOI.SingleVariable}, +) + MOI.throw_if_not_valid(uf, ci) + return MOI.SingleVariable(MOI.VariableIndex(ci.value)) +end +function MOI.get( + uf::UniversalFallback, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, +) where {S} + if MOI.supports_constraint(uf.model, MOI.SingleVariable, S) + MOI.get(uf.model, MOI.ConstraintSet(), ci) else MOI.throw_if_not_valid(uf, ci) - uf.constraints[(F, S)][ci][2] + return uf.single_variable_constraints[(F, S)][ci][2] end end + function MOI.set( uf::UniversalFallback, - ::MOI.ConstraintFunction, - ci::CI{F,S}, - func::F, -) where {F,S} - if MOI.supports_constraint(uf.model, F, S) - MOI.set(uf.model, MOI.ConstraintFunction(), ci, func) - else - MOI.throw_if_not_valid(uf, ci) - if F == MOI.SingleVariable - throw(MOI.SettingSingleVariableFunctionNotAllowed()) - end - (_, s) = uf.constraints[(F, S)][ci] - uf.constraints[(F, S)][ci] = (func, s) - end + attr::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{MOI.SingleVariable}, + func::MOI.SingleVariable, +) + return throw(MOI.SettingSingleVariableFunctionNotAllowed()) end function MOI.set( uf::UniversalFallback, ::MOI.ConstraintSet, - ci::CI{F,S}, + ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, set::S, -) where {F,S} - if MOI.supports_constraint(uf.model, F, S) +) where {S} + if MOI.supports_constraint(uf.model, MOI.SingleVariable, S) MOI.set(uf.model, MOI.ConstraintSet(), ci, set) else MOI.throw_if_not_valid(uf, ci) - (f, _) = uf.constraints[(F, S)][ci] - uf.constraints[(F, S)][ci] = (f, set) + (f, _) = uf.single_variable_constraints[(F, S)][ci] + uf.single_variable_constraints[(F, S)][ci] = (f, set) end end From 53eb140ff646d65ad6380d619ac144f74570ca22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 5 Mar 2021 13:23:03 +0100 Subject: [PATCH 7/9] Fixes --- src/Utilities/DoubleDicts.jl | 2 +- src/Utilities/model.jl | 63 ++++--- src/Utilities/universalfallback.jl | 218 ++++++++++++------------- src/Utilities/vector_of_constraints.jl | 71 ++++---- test/Utilities/universalfallback.jl | 6 +- 5 files changed, 180 insertions(+), 180 deletions(-) diff --git a/src/Utilities/DoubleDicts.jl b/src/Utilities/DoubleDicts.jl index 48f0c7e4e6..d9f3b5b879 100644 --- a/src/Utilities/DoubleDicts.jl +++ b/src/Utilities/DoubleDicts.jl @@ -19,7 +19,7 @@ Works as a `AbstractDict{CI, V}` with minimal differences. Note that `CI` is not a concrete type, opposed to `CI{MOI.SingleVariable, MOI.Integers}`, which is a concrete type. -When optimal performance or type stability is required its possible to obtain a +When optimal performance or type stability is required it is possible to obtain a fully type stable dictionary with values of type `V` and keys of type `CI{MOI.SingleVariable, MOI.Integers}` from the dictionary `dict`, for instance: diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 58ebce88d7..c82ccc691c 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,19 @@ function _delete_variable( ) end function MOI.delete(model::AbstractModel, vi::MOI.VariableIndex) + vis = [vi] + broadcastcall(model) do constrs + _throw_if_cannot_delete(constrs, vis, vis) + end _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) + broadcastcall(model) do constrs + _deleted_constraints(constrs, vi) do ci + delete!(model.con_to_name, ci) + end + 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 +125,22 @@ 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) + fast_in_vis = Set(vis) + broadcastcall(model) do constrs + _throw_if_cannot_delete(constrs, vis, fast_in_vis) + end + broadcastcall(model) do constrs + _deleted_constraints(constrs, vis) do ci + delete!(model.con_to_name, ci) + end 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( @@ -555,14 +556,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, @@ -580,11 +573,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(constraints(model, ci), attr, ci, func_or_set) end function MOI.get( @@ -700,7 +693,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, ) 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..e96bf9f308 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -101,6 +101,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 +114,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 +121,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 From 8afdfe2571730954745f4d3d84ba619a0d0adc8b Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 11 Mar 2021 10:31:15 +1300 Subject: [PATCH 8/9] Rebase and formatting fixes --- src/Utilities/CleverDicts.jl | 1 + src/Utilities/model.jl | 49 ++++++--- src/Utilities/universalfallback.jl | 33 ++++-- src/Utilities/vector_of_constraints.jl | 144 ++++++++++++++++++------- 4 files changed, 164 insertions(+), 63 deletions(-) diff --git a/src/Utilities/CleverDicts.jl b/src/Utilities/CleverDicts.jl index 47683d9def..ba3260a81c 100644 --- a/src/Utilities/CleverDicts.jl +++ b/src/Utilities/CleverDicts.jl @@ -374,6 +374,7 @@ function map_values!(f::Function, d::CleverDict) d.dict[k] = f(v) end end + return end end diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index c82ccc691c..861b804c47 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -8,11 +8,12 @@ const AbstractModel{T} = Union{AbstractModelLike{T},AbstractOptimizer{T}} # Variables function MOI.get(model::AbstractModel, ::MOI.NumberOfVariables)::Int64 if model.variable_indices === nothing - model.num_variables_created + return model.num_variables_created else - length(model.variable_indices) + return length(model.variable_indices) end end + function MOI.add_variable(model::AbstractModel{T}) where {T} vi = VI(model.num_variables_created += 1) push!(model.single_variable_mask, 0x0) @@ -23,6 +24,7 @@ function MOI.add_variable(model::AbstractModel{T}) where {T} end return vi end + function MOI.add_variables(model::AbstractModel, n::Integer) return [MOI.add_variable(model) for i in 1:n] end @@ -119,6 +121,7 @@ function MOI.delete(model::AbstractModel, vi::MOI.VariableIndex) model.name_to_con = nothing return end + function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex}) if isempty(vis) # In `keep`, we assume that `model.variable_indices !== nothing` so @@ -152,6 +155,7 @@ 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) @@ -159,6 +163,7 @@ function MOI.is_valid(model::AbstractModel, ci::CI{F,S}) where {F,S} return false end end + function MOI.is_valid(model::AbstractModel, vi::VI) if model.variable_indices === nothing return 1 ≤ vi.value ≤ model.num_variables_created @@ -187,8 +192,10 @@ MOI.get(model::AbstractModel, ::MOI.Name) = model.name MOI.supports(::AbstractModel, ::MOI.VariableName, vi::Type{VI}) = true function MOI.set(model::AbstractModel, ::MOI.VariableName, vi::VI, name::String) model.var_to_name[vi] = name - return model.name_to_var = nothing # Invalidate the name map. + model.name_to_var = nothing # Invalidate the name map. + return end + function MOI.get(model::AbstractModel, ::MOI.VariableName, vi::VI) return get(model.var_to_name, vi, EMPTYSTRING) end @@ -252,8 +259,10 @@ function MOI.set( name::String, ) model.con_to_name[ci] = name - return model.name_to_con = nothing # Invalidate the name map. + model.name_to_con = nothing # Invalidate the name map. + return end + function MOI.get(model::AbstractModel, ::MOI.ConstraintName, ci::CI) return get(model.con_to_name, ci, EMPTYSTRING) end @@ -285,11 +294,7 @@ function MOI.get(model::AbstractModel, ConType::Type{<:CI}, name::String) end ci = get(model.name_to_con, name, nothing) throw_if_multiple_with_name(ci, name) - if ci isa ConType - return ci - else - return nothing - end + return ci isa ConType ? ci : nothing end function MOI.get( @@ -312,8 +317,10 @@ function MOI.set( model.objective = zero(MOI.ScalarAffineFunction{T}) end model.senseset = true - return model.sense = sense + model.sense = sense + return end + function MOI.get(model::AbstractModel, ::MOI.ObjectiveFunctionType) return MOI.typeof(model.objective) end @@ -340,7 +347,8 @@ function MOI.set( end model.objectiveset = true # f needs to be copied, see #2 - return model.objective = copy(f) + model.objective = copy(f) + return end function MOI.modify( @@ -529,15 +537,20 @@ function _delete_constraint( ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, ) where {S} MOI.throw_if_not_valid(model, ci) - return model.single_variable_mask[ci.value] &= ~single_variable_flag(S) + model.single_variable_mask[ci.value] &= ~single_variable_flag(S) + return end + function _delete_constraint(model::AbstractModel, ci::MOI.ConstraintIndex) - return MOI.delete(constraints(model, ci), ci) + MOI.delete(constraints(model, ci), ci) + return end + function MOI.delete(model::AbstractModel, ci::MOI.ConstraintIndex) _delete_constraint(model, ci) model.name_to_con = nothing - return delete!(model.con_to_name, ci) + delete!(model.con_to_name, ci) + return end function MOI.modify( @@ -545,7 +558,8 @@ function MOI.modify( ci::MOI.ConstraintIndex, change::MOI.AbstractFunctionModification, ) - return MOI.modify(constraints(model, ci), ci, change) + MOI.modify(constraints(model, ci), ci, change) + return end function MOI.set( @@ -570,6 +584,7 @@ function MOI.set( if !iszero(flag & UPPER_BOUND_MASK) model.upper_bound[ci.value] = extract_upper_bound(set) end + return end function MOI.set( model::AbstractModel, @@ -577,7 +592,8 @@ function MOI.set( ci::MOI.ConstraintIndex, func_or_set, ) - return MOI.set(constraints(model, ci), attr, ci, func_or_set) + MOI.set(constraints(model, ci), attr, ci, func_or_set) + return end function MOI.get( @@ -733,6 +749,7 @@ function MOI.empty!(model::AbstractModel{T}) where {T} empty!(model.con_to_name) model.name_to_con = nothing broadcastcall(MOI.empty!, model) + return end diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index eb4c252c86..93da5fd227 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -82,11 +82,14 @@ function MOI.empty!(uf::UniversalFallback) uf.name_to_con = nothing empty!(uf.modattr) empty!(uf.varattr) - return empty!(uf.conattr) + empty!(uf.conattr) + return end + function MOI.copy_to(uf::UniversalFallback, src::MOI.ModelLike; kws...) return MOIU.automatic_copy_to(uf, src; kws...) end + function supports_default_copy_to(uf::UniversalFallback, copy_names::Bool) return supports_default_copy_to(uf.model, copy_names) end @@ -116,9 +119,11 @@ function _delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{MOI.SingleVariab MOI.is_valid(uf, ci) || throw(MOI.InvalidIndex(ci)) delete!(uf.single_variable_constraints[S], ci) end + return end function _delete(uf::UniversalFallback, ci::MOI.ConstraintIndex) MOI.delete(constraints(uf, ci), ci) + return end function MOI.delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{F,S}) where {F,S} _delete(uf, ci) @@ -129,6 +134,7 @@ function MOI.delete(uf::UniversalFallback, ci::MOI.ConstraintIndex{F,S}) where { for d in values(uf.conattr) delete!(d, ci) end + return end function _remove_variable( uf::UniversalFallback, @@ -161,6 +167,7 @@ function MOI.delete(uf::UniversalFallback, vi::MOI.VariableIndex) end end end + return end function MOI.delete(uf::UniversalFallback, vis::Vector{MOI.VariableIndex}) fast_in_vis = Set(vis) @@ -190,6 +197,7 @@ function MOI.delete(uf::UniversalFallback, vis::Vector{MOI.VariableIndex}) end end end + return end # Attributes @@ -225,9 +233,9 @@ function MOI.get( attr::Union{MOI.AbstractOptimizerAttribute,MOI.AbstractModelAttribute}, ) if !MOI.is_copyable(attr) || MOI.supports(uf.model, attr) - MOI.get(uf.model, attr) + return MOI.get(uf.model, attr) else - _get(uf, attr) + return _get(uf, attr) end end function MOI.get( @@ -237,9 +245,9 @@ function MOI.get( ) where {F,S} if MOI.supports_constraint(uf.model, F, S) && (!MOI.is_copyable(attr) || MOI.supports(uf.model, attr, typeof(idx))) - MOI.get(uf.model, attr, idx) + return MOI.get(uf.model, attr, idx) else - _get(uf, attr, idx) + return _get(uf, attr, idx) end end function MOI.get( @@ -248,9 +256,9 @@ function MOI.get( idx::MOI.VariableIndex, ) if !MOI.is_copyable(attr) || MOI.supports(uf.model, attr, typeof(idx)) - MOI.get(uf.model, attr, idx) + return MOI.get(uf.model, attr, idx) else - _get(uf, attr, idx) + return _get(uf, attr, idx) end end function MOI.get( @@ -280,9 +288,9 @@ function MOI.get( ) where {S} F = MOI.SingleVariable if MOI.supports_constraint(uf.model, F, S) - MOI.get(uf.model, listattr) + return MOI.get(uf.model, listattr) else - collect(keys(get( + return collect(keys(get( uf.single_variable_constraints, S, OrderedDict{CI{F,S},S}(), @@ -359,7 +367,8 @@ function MOI.set( if sense == MOI.FEASIBILITY_SENSE uf.objective = nothing end - return MOI.set(uf.model, attr, sense) + MOI.set(uf.model, attr, sense) + return end function MOI.get(uf::UniversalFallback, attr::MOI.ObjectiveFunctionType) if uf.objective === nothing @@ -394,6 +403,7 @@ function MOI.set( MOI.set(uf.model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) MOI.set(uf.model, MOI.ObjectiveSense(), sense) end + return end function MOI.modify( @@ -406,6 +416,7 @@ function MOI.modify( else uf.objective = modify_function(uf.objective, change) end + return end # Name @@ -628,6 +639,7 @@ function MOI.modify( change::MOI.AbstractFunctionModification, ) MOI.modify(constraints(uf, ci), ci, change) + return end function MOI.get( @@ -688,6 +700,7 @@ function MOI.set( MOI.throw_if_not_valid(uf, ci) uf.single_variable_constraints[S][ci] = set end + return end # Variables diff --git a/src/Utilities/vector_of_constraints.jl b/src/Utilities/vector_of_constraints.jl index e96bf9f308..9b8af523a0 100644 --- a/src/Utilities/vector_of_constraints.jl +++ b/src/Utilities/vector_of_constraints.jl @@ -15,12 +15,24 @@ # vector, it readily gives the entries of `model.constrmap` that need to be # updated. -struct VectorOfConstraints{F<:MOI.AbstractFunction,S<:MOI.AbstractSet} <: MOI.ModelLike - # FIXME It is not ideal that we have `DataType` here, it might induce type instabilities. - # We should change `CleverDicts` so that we can just use `typeof(CleverDicts.index_to_key)` here. - constraints::CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S},typeof(CleverDicts.key_to_index),Base.Fix1{typeof(CleverDicts.index_to_key),DataType}} +struct VectorOfConstraints{ + F<:MOI.AbstractFunction, + S<:MOI.AbstractSet +} <: MOI.ModelLike + # FIXME: It is not ideal that we have `DataType` here, it might induce type + # instabilities. We should change `CleverDicts` so that we can just + # use `typeof(CleverDicts.index_to_key)` here. + constraints::CleverDicts.CleverDict{ + MOI.ConstraintIndex{F,S}, + Tuple{F,S}, + typeof(CleverDicts.key_to_index), + Base.Fix1{typeof(CleverDicts.index_to_key),DataType} + } + function VectorOfConstraints{F,S}() where {F,S} - return new{F,S}(CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S}}()) + return new{F,S}( + CleverDicts.CleverDict{MOI.ConstraintIndex{F,S},Tuple{F,S}}() + ) end end @@ -30,34 +42,51 @@ MOI.empty!(v::VectorOfConstraints) = empty!(v.constraints) 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 - # it most of the time (they can check if they need to with `is_canonical`. + # We canonicalize the constraint so that solvers can avoid having to + # canonicalize it most of the time (they can check if they need to with + # `is_canonical`. # Note that the canonicalization is not guaranteed if for instance # `modify` is called and adds a new term. # See https://github.com/jump-dev/MathOptInterface.jl/pull/1118 return CleverDicts.add_item(v.constraints, (canonical(func), copy(set))) end + function MOI.is_valid( v::VectorOfConstraints{F,S}, - ci::MOI.ConstraintIndex{F,S} + ci::MOI.ConstraintIndex{F,S}, ) where {F,S} return haskey(v.constraints, ci) end -function MOI.delete(v::VectorOfConstraints{F,S}, ci::MOI.ConstraintIndex{F,S}) where {F,S} + +function MOI.delete( + v::VectorOfConstraints{F,S}, + ci::MOI.ConstraintIndex{F,S}, +) where {F,S} MOI.throw_if_not_valid(v, ci) delete!(v.constraints, ci) + return end -function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.ConstraintFunction, ci::MOI.ConstraintIndex{F,S}) where {F,S} + +function MOI.get( + v::VectorOfConstraints{F,S}, + ::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{F,S}, +) where {F,S} MOI.throw_if_not_valid(v, ci) return v.constraints[ci][1] end -function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.ConstraintSet, ci::MOI.ConstraintIndex{F,S}) where {F,S} + +function MOI.get( + v::VectorOfConstraints{F,S}, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{F,S}, +) where {F,S} MOI.throw_if_not_valid(v, ci) return v.constraints[ci][2] end + function MOI.set( v::VectorOfConstraints{F,S}, ::MOI.ConstraintFunction, @@ -68,6 +97,7 @@ function MOI.set( v.constraints[ci] = (func, v.constraints[ci][2]) return end + function MOI.set( v::VectorOfConstraints{F,S}, ::MOI.ConstraintSet, @@ -81,16 +111,25 @@ end function MOI.get( v::VectorOfConstraints{F,S}, - ::MOI.ListOfConstraints + ::MOI.ListOfConstraints, )::Vector{Tuple{DataType,DataType}} where {F,S} return isempty(v.constraints) ? [] : [(F, S)] end -function MOI.get(v::VectorOfConstraints{F,S}, ::MOI.NumberOfConstraints{F,S}) where {F,S} + +function MOI.get( + v::VectorOfConstraints{F,S}, + ::MOI.NumberOfConstraints{F,S}, +) where {F,S} return length(v.constraints) end -function MOI.get(v::VectorOfConstraints{F,S}, ci::MOI.ListOfConstraintIndices{F,S}) where {F,S} + +function MOI.get( + v::VectorOfConstraints{F,S}, + ::MOI.ListOfConstraintIndices{F,S}, +) where {F,S} return keys(v.constraints) end + function MOI.modify( v::VectorOfConstraints{F,S}, ci::MOI.ConstraintIndex{F,S}, @@ -105,13 +144,15 @@ end function _remove_variable(v::VectorOfConstraints, vi::MOI.VariableIndex) CleverDicts.map_values!(v.constraints) do func_set - remove_variable(func_set..., vi) + return remove_variable(func_set..., vi) end + return end function _filter_variables(keep::Function, v::VectorOfConstraints) CleverDicts.map_values!(v.constraints) do func_set - filter_variables(keep, func_set...) + return filter_variables(keep, func_set...) end + return end function throw_delete_variable_in_vov(vi::MOI.VariableIndex) @@ -121,29 +162,46 @@ function throw_delete_variable_in_vov(vi::MOI.VariableIndex) ) return throw(MOI.DeleteNotAllowed(vi, message)) end -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 + +# Nothing to do as it's not `VectorOfVariables` constraints +_throw_if_cannot_delete(::VectorOfConstraints, vis, fast_in_vis) = nothing + +function _throw_if_cannot_delete( + v::VectorOfConstraints{MOI.VectorOfVariables,S}, + vis, + fast_in_vis, +) where {S<:MOI.AbstractVectorSet} + if MOI.supports_dimension_update(S) + return + end + 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 end end + return end -function _delete_variables(::Function, ::VectorOfConstraints, ::Vector{MOI.VariableIndex}) - # Nothing to do as it's not `VectorOfVariables` constraints + +function _delete_variables( + ::Function, + ::VectorOfConstraints, + ::Vector{MOI.VariableIndex}, +) + return # 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} + +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 @@ -158,13 +216,25 @@ function _delete_variables(callback::Function, v::VectorOfConstraints{MOI.Vector end return end -function _deleted_constraints(callback::Function, v::VectorOfConstraints, vi::MOI.VariableIndex) + +function _deleted_constraints( + callback::Function, + v::VectorOfConstraints, + vi::MOI.VariableIndex, +) vis = [vi] _delete_variables(callback, v, vis) _remove_variable(v, vi) + return end -function _deleted_constraints(callback::Function, v::VectorOfConstraints, vis::Vector{MOI.VariableIndex}) + +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) + return end From b2b192e84705805202558fefda6930da8d73643d Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 11 Mar 2021 10:56:41 +1300 Subject: [PATCH 9/9] Fix docs --- docs/src/manual/basic_usage.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/manual/basic_usage.md b/docs/src/manual/basic_usage.md index 45d1459e0a..d2d2210237 100644 --- a/docs/src/manual/basic_usage.md +++ b/docs/src/manual/basic_usage.md @@ -291,7 +291,6 @@ MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) # output -MAX_SENSE::OptimizationSense = 1 ``` We add the knapsack constraint and integrality constraints: