From c2fbbf4d66fcc61a87fdc6675cdb4432d7ef7779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 16:08:52 +0200 Subject: [PATCH 01/12] Delete VectorOfVariables constraint when the function matches --- src/Utilities/functions.jl | 12 +-- src/Utilities/mockoptimizer.jl | 13 +++ src/Utilities/model.jl | 161 +++++++++++++++++++--------- src/Utilities/sets.jl | 4 +- src/Utilities/universalfallback.jl | 129 +++++++++++++++------- test/Utilities/model.jl | 20 +++- test/Utilities/universalfallback.jl | 60 +++++++++++ 7 files changed, 298 insertions(+), 101 deletions(-) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index b1a1ac7726..d46fc18ad1 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -460,25 +460,25 @@ function _rmvar(vis_or_terms::Vector, vi) end """ - removevariable(f::AbstractFunction, vi::VariableIndex) + remove_variable(f::AbstractFunction, vi::VariableIndex) Return a new function `f` with the variable vi removed. """ -function removevariable end -function removevariable(f::MOI.SingleVariable, vi::MOI.VariableIndex) +function remove_variable end +function remove_variable(f::MOI.SingleVariable, vi::MOI.VariableIndex) if f.variable == vi error("Cannot remove variable from a `SingleVariable` function of the", " same variable.") end return f end -function removevariable(f::VVF, vi) +function remove_variable(f::VVF, vi) VVF(_rmvar(f.variables, vi)) end -function removevariable(f::Union{SAF, VAF}, vi) +function remove_variable(f::Union{SAF, VAF}, vi) typeof(f)(_rmvar(f.terms, vi), MOI.constant(f)) end -function removevariable(f::Union{SQF, VQF}, vi) +function remove_variable(f::Union{SQF, VQF}, vi) terms = _rmvar.((f.affine_terms, f.quadratic_terms), Ref(vi)) typeof(f)(terms..., MOI.constant(f)) end diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index 73287726b1..0af8212893 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -385,6 +385,19 @@ function MOI.delete(mock::MockOptimizer, index::MOI.VariableIndex) MOI.delete(mock.inner_model, xor_index(index)) delete!(mock.varprimal, index) end +function MOI.delete(mock::MockOptimizer, indices::Vector{MOI.VariableIndex}) + if !mock.delete_allowed && !isempty(indices) + throw(MOI.DeleteNotAllowed(first(indices))) + end + for index in indices + # The index thrown by `mock.inner_model` would be xored + MOI.throw_if_not_valid(mock, index) + end + MOI.delete(mock.inner_model, xor_index.(indices)) + for index in indices + delete!(mock.varprimal, index) + end +end function MOI.delete(mock::MockOptimizer, index::MOI.ConstraintIndex) if !mock.delete_allowed throw(MOI.DeleteNotAllowed(index)) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 9d69737826..402d6402d3 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -103,7 +103,7 @@ function MOI.add_variables(model::AbstractModel, n::Integer) end """ - removevariable(f::MOI.AbstractFunction, s::MOI.AbstractSet, vi::MOI.VariableIndex) + remove_variable(f::MOI.AbstractFunction, s::MOI.AbstractSet, vi::MOI.VariableIndex) Return a tuple `(g, t)` representing the constraint `f`-in-`s` with the variable `vi` removed. That is, the terms containing the variable `vi` in the @@ -111,46 +111,74 @@ function `f` are removed and the dimension of the set `s` is updated if needed (e.g. when `f` is a `VectorOfVariables` with `vi` being one of the variables). """ -removevariable(f, s, vi::VI) = removevariable(f, vi), s -function removevariable(f::MOI.VectorOfVariables, s, vi::VI) - g = removevariable(f, vi) +remove_variable(f, s, vi::VI) = remove_variable(f, vi), s +function remove_variable(f::MOI.VectorOfVariables, s, vi::VI) + g = remove_variable(f, vi) if length(g.variables) != length(f.variables) - t = updatedimension(s, length(g.variables)) + t = update_dimension(s, length(g.variables)) else t = s end return g, t end -function _removevar!(constrs::Vector, vi::VI) +function _remove_variable(constrs::Vector, vi::VI) for i in eachindex(constrs) ci, f, s = constrs[i] - constrs[i] = (ci, removevariable(f, s, vi)...) + constrs[i] = (ci, remove_variable(f, s, vi)...) end return CI{MOI.SingleVariable}[] end -function _removevar!(constrs::Vector{<:ConstraintEntry{MOI.SingleVariable}}, - vi::VI) - # If a variable is removed, the SingleVariable constraints using this variable - # need to be removed too - rm = CI{MOI.SingleVariable}[] +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", + " other variables in a `MOI.VectorOfVariables`.") + 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 f.variable == vi - push!(rm, ci) + if vi in f.variables + if length(f.variables) > 1 + # If `s isa DimensionUpdatableSets` then the variable will be + # removed in `_remove_variable`. + if !(s isa DimensionUpdatableSets) + throw_delete_variable_in_vov(vi) + end + else + push!(rm, ci) + end end end return rm end -function MOI.delete(model::AbstractModel, vi::VI) - if !MOI.is_valid(model, vi) - throw(MOI.InvalidIndex(vi)) +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 - model.objective = removevariable(model.objective, vi) - # `ci_to_remove` is the list of indices of the `SingleVariable` constraints - # of `vi` - ci_to_remove = broadcastvcat(constrs -> _removevar!(constrs, vi), model) - for ci in ci_to_remove + return rm +end +function MOI.delete(model::AbstractModel{T}, vi::VI) where T + MOI.throw_if_not_valid(model, vi) + model.objective = remove_variable(model.objective, 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 + # `VectorOfVariables` constraints with non-`DimensionUpdatableSets` were + # either deleted or an error was thrown. The rest is modified now. + broadcastcall(constrs -> _remove_variable(constrs, vi), model) model.single_variable_mask[vi.value] = 0x0 if model.variable_indices === nothing model.variable_indices = Set(MOI.get(model, @@ -158,8 +186,24 @@ function MOI.delete(model::AbstractModel, vi::VI) end delete!(model.variable_indices, vi) model.name_to_var = nothing - if haskey(model.var_to_name, vi) - delete!(model.var_to_name, vi) + delete!(model.var_to_name, vi) + model.name_to_con = nothing + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{T}}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{T}}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{T}}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.ZeroOne}(vi.value)) +end +function MOI.delete(model::AbstractModel, vis::Vector{VI}) + # 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) + end + for vi in vis + MOI.delete(model, vi) end end @@ -214,26 +258,48 @@ function MOI.set(model::AbstractModel, ::MOI.VariableName, vi::VI, name::String) end MOI.get(model::AbstractModel, ::MOI.VariableName, vi::VI) = get(model.var_to_name, vi, EMPTYSTRING) +""" + build_name_to_var_map(con_to_name::Dict{MOI.VariableIndex, String}) + +Create and return a reverse map from name to variable index, given a map from +variable index to name. The special value `MOI.VariableIndex(0)` is used to +indicate that multiple variables have the same name. +""" +function build_name_to_var_map(var_to_name::Dict{VI, String}) + name_to_var = Dict{String, VI}() + for (var, var_name) in var_to_name + if haskey(name_to_var, var_name) + # 0 is a special value that means this string does not map to + # a unique variable name. + name_to_var[var_name] = VI(0) + else + name_to_var[var_name] = var + end + end + return name_to_var +end + +function throw_multiple_name_error(::Type{MOI.VariableIndex}, name::String) + error("Multiple variables have the name $name.") +end +function throw_multiple_name_error(::Type{<:MOI.ConstraintIndex}, name::String) + error("Multiple constraints have the name $name.") +end +function throw_if_multiple_with_name(::Nothing, ::String) end +function throw_if_multiple_with_name(index::MOI.Index, name::String) + if iszero(index.value) + throw_multiple_name_error(typeof(index), name) + end +end + function MOI.get(model::AbstractModel, ::Type{VI}, name::String) if model.name_to_var === nothing # Rebuild the map. - model.name_to_var = Dict{String, VI}() - for (var, var_name) in model.var_to_name - if haskey(model.name_to_var, var_name) - # -1 is a special value that means this string does not map to - # a unique variable name. - model.name_to_var[var_name] = VI(-1) - else - model.name_to_var[var_name] = var - end - end + model.name_to_var = build_name_to_var_map(model.var_to_name) end result = get(model.name_to_var, name, nothing) - if result == VI(-1) - error("Multiple variables have the name $name.") - else - return result - end + throw_if_multiple_with_name(result, name) + return result end function MOI.get(model::AbstractModel, ::MOI.ListOfVariableAttributesSet)::Vector{MOI.AbstractVariableAttribute} @@ -252,14 +318,14 @@ MOI.get(model::AbstractModel, ::MOI.ConstraintName, ci::CI) = get(model.con_to_n Create and return a reverse map from name to constraint index, given a map from constraint index to name. The special value -`MOI.ConstraintIndex{Nothing, Nothing}(-1)` is used to indicate that multiple +`MOI.ConstraintIndex{Nothing, Nothing}(0)` is used to indicate that multiple constraints have the same name. """ function build_name_to_con_map(con_to_name::Dict{CI, String}) name_to_con = Dict{String, CI}() for (con, con_name) in con_to_name if haskey(name_to_con, con_name) - name_to_con[con_name] = CI{Nothing, Nothing}(-1) + name_to_con[con_name] = CI{Nothing, Nothing}(0) else name_to_con[con_name] = con end @@ -274,9 +340,8 @@ function MOI.get(model::AbstractModel, ConType::Type{<:CI}, name::String) model.name_to_con = build_name_to_con_map(model.con_to_name) end ci = get(model.name_to_con, name, nothing) - if ci == CI{Nothing, Nothing}(-1) - error("Multiple constraints have the name $name.") - elseif ci isa ConType + throw_if_multiple_with_name(ci, name) + if ci isa ConType return ci else return nothing @@ -446,14 +511,10 @@ function _delete_constraint(model::AbstractModel, ci::CI) model.constrmap[ci.value] = 0 end function MOI.delete(model::AbstractModel, ci::CI) - if !MOI.is_valid(model, ci) - throw(MOI.InvalidIndex(ci)) - end + MOI.throw_if_not_valid(model, ci) _delete_constraint(model, ci) model.name_to_con = nothing - if haskey(model.con_to_name, ci) - delete!(model.con_to_name, ci) - end + delete!(model.con_to_name, ci) end function MOI.modify(model::AbstractModel, ci::CI, change::MOI.AbstractFunctionModification) diff --git a/src/Utilities/sets.jl b/src/Utilities/sets.jl index 9b526f66d5..4a5cc367e2 100644 --- a/src/Utilities/sets.jl +++ b/src/Utilities/sets.jl @@ -3,11 +3,11 @@ const DimensionUpdatableSets = Union{MOI.Reals, MOI.Nonnegatives, MOI.Nonpositives} """ - updatedimension(s::AbstractVectorSet, newdim) + update_dimension(s::AbstractVectorSet, newdim) Returns a set with the dimension modified to `newdim`. """ -function updatedimension(::S, newdim) where S<:DimensionUpdatableSets +function update_dimension(::S, newdim) where S<:DimensionUpdatableSets S(newdim) end diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index 46ce06ace4..c9550bc71f 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -73,30 +73,86 @@ function MOI.delete(uf::UniversalFallback, ci::CI{F, S}) where {F, S} throw(MOI.InvalidIndex(ci)) end delete!(uf.constraints[(F, S)], ci) - if haskey(uf.con_to_name, ci) - delete!(uf.con_to_name, ci) - end + delete!(uf.con_to_name, ci) uf.name_to_con = nothing end for d in values(uf.conattr) delete!(d, ci) end end +function _remove_variable(uf::UniversalFallback, + constraints::Dict{<: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 + MOI.delete(uf, to_delete) +end +function _remove_variable(uf::UniversalFallback, + constraints::Dict{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 S <: MOIU.DimensionUpdatableSets + constraints[ci] = remove_variable(f, s, vi) + else + throw_delete_variable_in_vov(vi) + end + else + push!(to_delete, ci) + end + end + end + MOI.delete(uf, to_delete) +end +function _remove_variable(::UniversalFallback, constraints::Dict{<:CI}, vi::VI) + for (ci, constraint) in constraints + f, s = constraint + constraints[ci] = remove_variable(f, s, vi) + end +end +function _remove_vector_of_variables( + uf::UniversalFallback, constraints::Dict{<: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 + end + MOI.delete(uf, to_delete) +end +function _remove_vector_of_variables( + ::UniversalFallback, ::Dict{<: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) end - for (FS, constraints) in uf.constraints - for (ci, constraint) in constraints - f, s = constraint - if f isa MOI.SingleVariable - if f.variable == vi - delete!(constraints, ci) - end - else - constraints[ci] = removevariable(f, s, vi) - end + for (_, constraints) in uf.constraints + _remove_variable(uf, constraints, vi) + end +end +function MOI.delete(uf::UniversalFallback, vis::Vector{VI}) + MOI.delete(uf.model, vis) + for d in values(uf.varattr) + for vi in vis + delete!(d, vi) + end + end + for (_, constraints) in uf.constraints + _remove_vector_of_variables(uf, constraints, vis) + for vi in vis + _remove_variable(uf, constraints, vi) end end end @@ -214,47 +270,38 @@ function MOI.get(uf::UniversalFallback, attr::MOI.ConstraintName, ci::CI{F, S}) end MOI.get(uf::UniversalFallback, ::Type{VI}, name::String) = MOI.get(uf.model, VI, name) + +check_type_and_multiple_names(::Type, ::Nothing, ::Nothing, name) = nothing +check_type_and_multiple_names(::Type{T}, value::T, ::Nothing, name) where T = value +check_type_and_multiple_names(::Type, ::Any, ::Nothing, name) where T = nothing +check_type_and_multiple_names(::Type{T}, ::Nothing, value::T, name) where T = value +check_type_and_multiple_names(::Type, ::Nothing, ::Any, name) where T = nothing +function check_type_and_multiple_names(T::Type, ::Any, ::Any, name) + throw_multiple_name_error(T, name) +end function MOI.get(uf::UniversalFallback, ::Type{CI{F, S}}, name::String) where {F, S} if uf.name_to_con === nothing uf.name_to_con = build_name_to_con_map(uf.con_to_name) end - if MOI.supports_constraint(uf.model, F, S) ci = MOI.get(uf.model, CI{F, S}, name) - if ci !== nothing && haskey(uf.name_to_con, name) - error("Multiple constraints have the name $name.") - end - return ci else - ci = get(uf.name_to_con, name, nothing) - if ci == CI{Nothing, Nothing}(-1) - error("Multiple constraints have the name $name.") - elseif ci isa CI{F, S} - return ci - else - return nothing - end + # There is no `F`-in-`S` constraint in `b.model`, `ci` is only got + # to check for duplicate names. + ci = MOI.get(uf.model, CI, name) end + ci_uf = get(uf.name_to_con, name, nothing) + throw_if_multiple_with_name(ci_uf, name) + return check_type_and_multiple_names(CI{F, S}, ci_uf, ci, name) end function MOI.get(uf::UniversalFallback, ::Type{CI}, name::String) if uf.name_to_con === nothing uf.name_to_con = build_name_to_con_map(uf.con_to_name) end - - ci = MOI.get(uf.model, CI, name) - if ci === nothing - uf_ci = get(uf.name_to_con, name, nothing) - if uf_ci == CI{Nothing, Nothing}(-1) - error("Multiple constraints have the name $name.") - else - return uf_ci - end - else - if haskey(uf.name_to_con, name) - error("Multiple constraints have the name $name.") - end - return ci - end + ci_uf = get(uf.name_to_con, name, nothing) + throw_if_multiple_with_name(ci_uf, name) + return check_type_and_multiple_names( + CI, ci_uf, MOI.get(uf.model, CI, name), name) end _set(uf, attr::MOI.AbstractOptimizerAttribute, value) = uf.optattr[attr] = value diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index b27e0dedeb..793cb8e8d2 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -130,6 +130,10 @@ end c7 = MOI.add_constraint(model, f7, MOI.Nonpositives(2)) @test 1 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonpositives}()) + f8 = MOI.VectorOfVariables([x, y]) + c8 = MOI.add_constraint(model, f8, MOI.SecondOrderCone(2)) + @test 1 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.SecondOrderCone}()) + loc1 = MOI.get(model, MOI.ListOfConstraints()) loc2 = Vector{Tuple{DataType, DataType}}() function _pushloc(constrs::Vector{MOIU.ConstraintEntry{F, S}}) where {F, S} @@ -139,7 +143,7 @@ end end MOIU.broadcastcall(_pushloc, model) for loc in (loc1, loc2) - @test length(loc) == 5 + @test length(loc) == 6 @test (MOI.VectorQuadraticFunction{Int},MOI.PositiveSemidefiniteConeTriangle) in loc @test (MOI.VectorQuadraticFunction{Int},MOI.PositiveSemidefiniteConeTriangle) in loc @test (MOI.VectorOfVariables,MOI.RotatedSecondOrderCone) in loc @@ -154,6 +158,16 @@ end @test 1 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Int},MOI.SecondOrderCone}()) @test MOI.get(model, MOI.ConstraintFunction(), c6).constants == f6.constants + message = string("Cannot delete variable as it is constrained with other", + " other variables in a `MOI.VectorOfVariables`.") + err = MOI.DeleteNotAllowed(y, message) + @test_throws err MOI.delete(model, y) + + @test MOI.is_valid(model, c8) + MOI.delete(model, c8) + @test !MOI.is_valid(model, c8) + @test 0 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.SecondOrderCone}()) + MOI.delete(model, y) f = MOI.get(model, MOI.ConstraintFunction(), c2) @@ -165,12 +179,14 @@ end @test f.terms == MOI.VectorAffineTerm.([1], MOI.ScalarAffineTerm.([2], [x])) @test f.constants == [6, 8] + @test 1 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonpositives}()) + @test [c7] == @inferred MOI.get(model, MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.Nonpositives}()) + f = MOI.get(model, MOI.ConstraintFunction(), c7) @test f.variables == [x] s = MOI.get(model, MOI.ConstraintSet(), c7) @test MOI.dimension(s) == 1 - end # We create a new function and set to test catching errors if users create their diff --git a/test/Utilities/universalfallback.jl b/test/Utilities/universalfallback.jl index 47283486e7..5219f3c30c 100644 --- a/test/Utilities/universalfallback.jl +++ b/test/Utilities/universalfallback.jl @@ -204,3 +204,63 @@ end "a name") @test_throws Exception MOI.get(uf, MOI.ConstraintIndex, "a name") end + +@testset "Delete" begin + model = ModelForUniversalFallback{Float64}() + uf = MOIU.UniversalFallback(model) + x, cx = MOI.add_constrained_variable(uf, MOI.GreaterThan(0.0)) + y, cy = MOI.add_constrained_variables(uf, MOI.Nonpositives(4)) + @test MOI.is_valid(uf, x) + @test MOI.is_valid(uf, y[1]) + @test MOI.is_valid(uf, y[2]) + @test MOI.is_valid(uf, y[3]) + @test MOI.is_valid(uf, y[4]) + @test MOI.is_valid(uf, cx) + @test MOI.is_valid(uf, cy) + @test MOI.get(uf, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) + @test MOI.get(uf, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) + @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y) + @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(4) + MOI.delete(uf, y[3]) + @test MOI.is_valid(uf, x) + @test MOI.is_valid(uf, y[1]) + @test MOI.is_valid(uf, y[2]) + @test !MOI.is_valid(uf, y[3]) + @test MOI.is_valid(uf, y[4]) + @test MOI.is_valid(uf, cx) + @test MOI.is_valid(uf, cy) + @test MOI.get(uf, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) + @test MOI.get(uf, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) + @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[1, 2, 4]]) + @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(3) + MOI.delete(uf, y[1]) + @test MOI.is_valid(uf, x) + @test !MOI.is_valid(uf, y[1]) + @test MOI.is_valid(uf, y[2]) + @test !MOI.is_valid(uf, y[3]) + @test MOI.is_valid(uf, y[4]) + @test MOI.is_valid(uf, cx) + @test MOI.is_valid(uf, cy) + @test MOI.get(uf, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) + @test MOI.get(uf, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) + @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) + @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) + MOI.delete(uf, x) + @test !MOI.is_valid(uf, x) + @test !MOI.is_valid(uf, y[1]) + @test MOI.is_valid(uf, y[2]) + @test !MOI.is_valid(uf, y[3]) + @test MOI.is_valid(uf, y[4]) + @test !MOI.is_valid(uf, cx) + @test MOI.is_valid(uf, cy) + @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) + @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) + MOI.delete(uf, y[[2, 4]]) + @test !MOI.is_valid(uf, x) + @test !MOI.is_valid(uf, y[1]) + @test !MOI.is_valid(uf, y[2]) + @test !MOI.is_valid(uf, y[3]) + @test !MOI.is_valid(uf, y[4]) + @test !MOI.is_valid(uf, cx) + @test !MOI.is_valid(uf, cy) +end From 29a1938ba4fcbc32480d7f870c72da86c052fe63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 16:28:10 +0200 Subject: [PATCH 02/12] Add standalone test --- src/Test/modellike.jl | 60 +++++++++++++++++++++++++++++ test/Utilities/mockoptimizer.jl | 5 +++ test/Utilities/model.jl | 4 ++ test/Utilities/universalfallback.jl | 56 +-------------------------- 4 files changed, 70 insertions(+), 55 deletions(-) diff --git a/src/Test/modellike.jl b/src/Test/modellike.jl index 88494d0057..c54b5947b3 100644 --- a/src/Test/modellike.jl +++ b/src/Test/modellike.jl @@ -471,3 +471,63 @@ function set_upper_bound_twice(model::MOI.ModelLike, T::Type) MOI.delete(model, ci) end end + +function delete_test(model::MOI.ModelLike) + x = MOI.add_variable(model) + cx = MOI.add_constraint(model, x, MOI.GreaterThan(0.0)) + y = MOI.add_variables(model, 4) + cy = MOI.add_constraint(model, y, MOI.Nonpositives(4)) + @test MOI.is_valid(model, x) + @test MOI.is_valid(model, y[1]) + @test MOI.is_valid(model, y[2]) + @test MOI.is_valid(model, y[3]) + @test MOI.is_valid(model, y[4]) + @test MOI.is_valid(model, cx) + @test MOI.is_valid(model, cy) + @test MOI.get(model, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) + @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) + @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y) + @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(4) + MOI.delete(model, y[3]) + @test MOI.is_valid(model, x) + @test MOI.is_valid(model, y[1]) + @test MOI.is_valid(model, y[2]) + @test !MOI.is_valid(model, y[3]) + @test MOI.is_valid(model, y[4]) + @test MOI.is_valid(model, cx) + @test MOI.is_valid(model, cy) + @test MOI.get(model, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) + @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) + @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[1, 2, 4]]) + @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(3) + MOI.delete(model, y[1]) + @test MOI.is_valid(model, x) + @test !MOI.is_valid(model, y[1]) + @test MOI.is_valid(model, y[2]) + @test !MOI.is_valid(model, y[3]) + @test MOI.is_valid(model, y[4]) + @test MOI.is_valid(model, cx) + @test MOI.is_valid(model, cy) + @test MOI.get(model, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) + @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) + @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) + @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) + MOI.delete(model, x) + @test !MOI.is_valid(model, x) + @test !MOI.is_valid(model, y[1]) + @test MOI.is_valid(model, y[2]) + @test !MOI.is_valid(model, y[3]) + @test MOI.is_valid(model, y[4]) + @test !MOI.is_valid(model, cx) + @test MOI.is_valid(model, cy) + @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) + @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) + MOI.delete(model, y[[2, 4]]) + @test !MOI.is_valid(model, x) + @test !MOI.is_valid(model, y[1]) + @test !MOI.is_valid(model, y[2]) + @test !MOI.is_valid(model, y[3]) + @test !MOI.is_valid(model, y[4]) + @test !MOI.is_valid(model, cx) + @test !MOI.is_valid(model, cy) +end diff --git a/test/Utilities/mockoptimizer.jl b/test/Utilities/mockoptimizer.jl index ef9f6b78d2..204deef88f 100644 --- a/test/Utilities/mockoptimizer.jl +++ b/test/Utilities/mockoptimizer.jl @@ -90,3 +90,8 @@ end @test MOI.get(optimizer, MOI.ConstraintDual(), c1) == 5.9 @test MOI.get(optimizer, MOI.ConstraintDual(), soc) == [1.0,2.0] end + +@testset "Delete" begin + mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) + MOIT.delete_test(mock) +end diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index 793cb8e8d2..a9f8f2a28f 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -59,6 +59,10 @@ end MOIT.validtest(MOIU.Model{Float64}()) end +@testset "Delete test" begin + MOIT.delete_test(MOIU.Model{Float64}()) +end + @testset "Empty test" begin MOIT.emptytest(MOIU.Model{Float64}()) end diff --git a/test/Utilities/universalfallback.jl b/test/Utilities/universalfallback.jl index 5219f3c30c..2bf41a9640 100644 --- a/test/Utilities/universalfallback.jl +++ b/test/Utilities/universalfallback.jl @@ -208,59 +208,5 @@ end @testset "Delete" begin model = ModelForUniversalFallback{Float64}() uf = MOIU.UniversalFallback(model) - x, cx = MOI.add_constrained_variable(uf, MOI.GreaterThan(0.0)) - y, cy = MOI.add_constrained_variables(uf, MOI.Nonpositives(4)) - @test MOI.is_valid(uf, x) - @test MOI.is_valid(uf, y[1]) - @test MOI.is_valid(uf, y[2]) - @test MOI.is_valid(uf, y[3]) - @test MOI.is_valid(uf, y[4]) - @test MOI.is_valid(uf, cx) - @test MOI.is_valid(uf, cy) - @test MOI.get(uf, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) - @test MOI.get(uf, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) - @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y) - @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(4) - MOI.delete(uf, y[3]) - @test MOI.is_valid(uf, x) - @test MOI.is_valid(uf, y[1]) - @test MOI.is_valid(uf, y[2]) - @test !MOI.is_valid(uf, y[3]) - @test MOI.is_valid(uf, y[4]) - @test MOI.is_valid(uf, cx) - @test MOI.is_valid(uf, cy) - @test MOI.get(uf, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) - @test MOI.get(uf, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) - @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[1, 2, 4]]) - @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(3) - MOI.delete(uf, y[1]) - @test MOI.is_valid(uf, x) - @test !MOI.is_valid(uf, y[1]) - @test MOI.is_valid(uf, y[2]) - @test !MOI.is_valid(uf, y[3]) - @test MOI.is_valid(uf, y[4]) - @test MOI.is_valid(uf, cx) - @test MOI.is_valid(uf, cy) - @test MOI.get(uf, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x) - @test MOI.get(uf, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) - @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) - @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) - MOI.delete(uf, x) - @test !MOI.is_valid(uf, x) - @test !MOI.is_valid(uf, y[1]) - @test MOI.is_valid(uf, y[2]) - @test !MOI.is_valid(uf, y[3]) - @test MOI.is_valid(uf, y[4]) - @test !MOI.is_valid(uf, cx) - @test MOI.is_valid(uf, cy) - @test MOI.get(uf, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) - @test MOI.get(uf, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) - MOI.delete(uf, y[[2, 4]]) - @test !MOI.is_valid(uf, x) - @test !MOI.is_valid(uf, y[1]) - @test !MOI.is_valid(uf, y[2]) - @test !MOI.is_valid(uf, y[3]) - @test !MOI.is_valid(uf, y[4]) - @test !MOI.is_valid(uf, cx) - @test !MOI.is_valid(uf, cy) + MOIT.delete_test(uf) end From e7f21c3e31e38072b6282f0493f8d6a6f9e52f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 16:55:00 +0200 Subject: [PATCH 03/12] Update remove_variable call --- src/Bridges/Constraint/slack.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/Constraint/slack.jl b/src/Bridges/Constraint/slack.jl index 0d681aaa72..4b887ca1b7 100644 --- a/src/Bridges/Constraint/slack.jl +++ b/src/Bridges/Constraint/slack.jl @@ -93,7 +93,7 @@ end function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, b::ScalarSlackBridge{T}) where T - return MOIU.removevariable(MOI.get(model, attr, b.equality), b.slack) + return MOIU.remove_variable(MOI.get(model, attr, b.equality), b.slack) end function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, b::ScalarSlackBridge) @@ -191,7 +191,7 @@ end function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, b::VectorSlackBridge{T}) where T - return MOIU.removevariable(MOI.get(model, attr, b.equality), b.slacks) + return MOIU.remove_variable(MOI.get(model, attr, b.equality), b.slacks) end function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, b::VectorSlackBridge) From 859061ef83dcc57b46ad0c125b228ced4e921261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 16:55:08 +0200 Subject: [PATCH 04/12] Update doc --- docs/src/apireference.md | 37 +++++++++++++++++++++++++++++++++++++ src/Utilities/sets.jl | 34 ++++++++++++++++++++++++++-------- src/indextypes.jl | 18 +++++++++++++++++- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 2bdb4f5b46..e08822ef04 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -612,6 +612,43 @@ Utilities.state Utilities.mode ``` +## Function utilities + +The following utilities are available for functions: +```@docs +Utilities.evalvariables +Utilities.remove_variable +Utilities.all_coefficients +Utilities.unsafe_add +Utilities.isapprox_zero +Utilities.modifyfunction +``` + +The following functions can be used to canonicalize a function: +```@docs +Utilities.iscanonical +Utilities.canonical +Utilities.canonicalize! +``` + +The following functions can be used to manipulate functions with basic algebra: +```@docs +Utilities.scalar_type +Utilities.promote_operation +Utilities.operate +Utilities.operate! +Utilities.vectorize +``` + +## Set utilities + +The following utilities are available for sets: +```@docs +Utilities.supports_dimension_update +Utilities.update_dimension +Utilities.shift_constant +``` + ## Benchmarks Functions to help benchmark the performance of solver wrappers. See diff --git a/src/Utilities/sets.jl b/src/Utilities/sets.jl index 4a5cc367e2..c5684b2eca 100644 --- a/src/Utilities/sets.jl +++ b/src/Utilities/sets.jl @@ -1,14 +1,32 @@ -const DimensionUpdatableSets = Union{MOI.Reals, - MOI.Zeros, - MOI.Nonnegatives, - MOI.Nonpositives} """ - update_dimension(s::AbstractVectorSet, newdim) + supports_dimension_update(S::Type{<:MOI.AbstractVectorSet}) -Returns a set with the dimension modified to `newdim`. +Return a `Bool` indicating whether the elimination of any dimension of +`n`-dimensional sets of type `S` give an `n-1`-dimensional set `S`. +By default, this function returns `false` so it should only be implemented +for sets that supports dimension update. + +For instance, `supports_dimension_update(MOI.Nonnegatives}` is `true` because +the elimination of any dimension of the `n`-dimensional nonnegative orthant +gives the `n-1`-dimensional nonnegative orthant. However +`supports_dimension_update(MOI.ExponentialCone}` is `false`. +""" +function supports_dimension_update(::Type{<:MOI.AbstractVectorSet}) + return false +end +function supports_dimension_update(::Union{ + MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}) + return true +end + +""" + update_dimension(s::AbstractVectorSet, new_dim) + +Returns a set with the dimension modified to `new_dim`. """ -function update_dimension(::S, newdim) where S<:DimensionUpdatableSets - S(newdim) +function update_dimension(S::Union{ + MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}, new_dim) + return S(new_dim) end """ diff --git a/src/indextypes.jl b/src/indextypes.jl index 8954a64506..0a4560e909 100644 --- a/src/indextypes.jl +++ b/src/indextypes.jl @@ -95,7 +95,23 @@ end """ delete(model::ModelLike, index::Index) -Delete the referenced object from the model. +Delete the referenced object from the model. Throw [`DeleteNotAllowed`](@ref) if +if `index` cannot be deleted. + +The following modifications also take effect if `Index` is [`VariableIndex`](@ref): +* If `index` used in the objective function, it is removed from the function; + see [`Utilities.remove_variable`](@ref). +* For each `func`-in-`set` constraint of the model: + - If `func isa SingleVariable` and `func.variable == index` then the + constraint is deleted. + - If `func isa VectorOfVariables` and `index in func.variable` then + * if `length(func.variable) == 1` is one, the constraint is deleted; + * if `length(func.variable) > 1` and `supports_dimension_update(set)` then + then the variable is removed from `func` and `set` is replaced by + `update_dimension(set, MOI.dimension(set) - 1)`. + * Otherwise, a [`DeleteNotAllowed`](@ref) error is thrown. + - Otherwise, the variable is removed from `func`; see + [`Utilities.remove_variable`](@ref). """ delete(model::ModelLike, index::Index) = throw(DeleteNotAllowed(index)) From 689db299da48378693c09a797ea69c030bc203db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 17:36:06 -0400 Subject: [PATCH 05/12] Fixes --- src/Utilities/model.jl | 10 +++++----- src/Utilities/sets.jl | 8 ++++---- src/Utilities/universalfallback.jl | 2 +- test/Utilities/model.jl | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 402d6402d3..5fb31004cc 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -133,7 +133,7 @@ function _vector_of_variables_with(::Vector, ::Union{VI, MOI.Vector{VI}}) end function throw_delete_variable_in_vov(vi::VI) message = string("Cannot delete variable as it is constrained with other", - " other variables in a `MOI.VectorOfVariables`.") + " variables in a `MOI.VectorOfVariables`.") throw(MOI.DeleteNotAllowed(vi, message)) end function _vector_of_variables_with( @@ -142,9 +142,9 @@ function _vector_of_variables_with( for (ci, f, s) in constrs if vi in f.variables if length(f.variables) > 1 - # If `s isa DimensionUpdatableSets` then the variable will be + # If `supports_dimension_update(s)` then the variable will be # removed in `_remove_variable`. - if !(s isa DimensionUpdatableSets) + if !supports_dimension_update(typeof(s)) throw_delete_variable_in_vov(vi) end else @@ -176,8 +176,8 @@ function MOI.delete(model::AbstractModel{T}, vi::VI) where T for ci in vov_to_remove MOI.delete(model, ci) end - # `VectorOfVariables` constraints with non-`DimensionUpdatableSets` were - # either deleted or an error was thrown. The rest is modified now. + # `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) model.single_variable_mask[vi.value] = 0x0 if model.variable_indices === nothing diff --git a/src/Utilities/sets.jl b/src/Utilities/sets.jl index c5684b2eca..aa6e178def 100644 --- a/src/Utilities/sets.jl +++ b/src/Utilities/sets.jl @@ -14,8 +14,8 @@ gives the `n-1`-dimensional nonnegative orthant. However function supports_dimension_update(::Type{<:MOI.AbstractVectorSet}) return false end -function supports_dimension_update(::Union{ - MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}) +function supports_dimension_update(::Type{<:Union{ + MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}}) return true end @@ -24,9 +24,9 @@ end Returns a set with the dimension modified to `new_dim`. """ -function update_dimension(S::Union{ +function update_dimension(set::Union{ MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}, new_dim) - return S(new_dim) + return typeof(set)(new_dim) end """ diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index c9550bc71f..4338fc1c6d 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -99,7 +99,7 @@ function _remove_variable(uf::UniversalFallback, f::MOI.VectorOfVariables, s = constraint if vi in f.variables if length(f.variables) > 1 - if S <: MOIU.DimensionUpdatableSets + if supports_dimension_update(S) constraints[ci] = remove_variable(f, s, vi) else throw_delete_variable_in_vov(vi) diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index a9f8f2a28f..78010f9a05 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -163,7 +163,7 @@ end @test MOI.get(model, MOI.ConstraintFunction(), c6).constants == f6.constants message = string("Cannot delete variable as it is constrained with other", - " other variables in a `MOI.VectorOfVariables`.") + " variables in a `MOI.VectorOfVariables`.") err = MOI.DeleteNotAllowed(y, message) @test_throws err MOI.delete(model, y) From d2f3f260965e0adfa03f4ea2ea74b427dd7e47d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 18:20:01 -0400 Subject: [PATCH 06/12] Rename evalvariables and modifyobjective --- docs/src/apireference.md | 2 +- src/Utilities/functions.jl | 34 ++++++++++---------- src/Utilities/model.jl | 4 +-- src/Utilities/results.jl | 4 +-- src/Utilities/universalfallback.jl | 2 +- test/Utilities/functions.jl | 50 +++++++++++++++--------------- test/Utilities/model.jl | 4 +-- 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index e08822ef04..6193ef7be1 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -616,7 +616,7 @@ Utilities.mode The following utilities are available for functions: ```@docs -Utilities.evalvariables +Utilities.eval_variables Utilities.remove_variable Utilities.all_coefficients Utilities.unsafe_add diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index d46fc18ad1..04b0c50ca6 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -1,30 +1,30 @@ using Test """ - evalvariables(varval::Function, f::AbstractFunction) + eval_variables(varval::Function, f::AbstractFunction) Returns the value of function `f` if each variable index `vi` is evaluated as `varval(vi)`. """ -function evalvariables end -evalvariables(varval::Function, f::SVF) = varval(f.variable) -evalvariables(varval::Function, f::VVF) = varval.(f.variables) -function evalvariables(varval::Function, f::SAF) +function eval_variables end +eval_variables(varval::Function, f::SVF) = varval(f.variable) +eval_variables(varval::Function, f::VVF) = varval.(f.variables) +function eval_variables(varval::Function, f::SAF) return mapreduce(t->evalterm(varval, t), +, f.terms, init=f.constant) end -function evalvariables(varval::Function, f::VAF) +function eval_variables(varval::Function, f::VAF) out = copy(f.constants) for t in f.terms out[t.output_index] += evalterm(varval, t.scalar_term) end out end -function evalvariables(varval::Function, f::SQF) +function eval_variables(varval::Function, f::SQF) init = zero(f.constant) lin = mapreduce(t->evalterm(varval, t), +, f.affine_terms, init=init) quad = mapreduce(t->evalterm(varval, t), +, f.quadratic_terms, init=init) return lin + quad + f.constant end -function evalvariables(varval::Function, f::VQF) +function eval_variables(varval::Function, f::VQF) out = copy(f.constants) for t in f.affine_terms out[t.output_index] += evalterm(varval, t.scalar_term) @@ -484,20 +484,20 @@ function remove_variable(f::Union{SQF, VQF}, vi) end """ - modifyfunction(f::AbstractFunction, change::AbstractFunctionModification) + modify_function(f::AbstractFunction, change::AbstractFunctionModification) Return a new function `f` modified according to `change`. """ -function modifyfunction(f::SAF, change::MOI.ScalarConstantChange) +function modify_function(f::SAF, change::MOI.ScalarConstantChange) return SAF(f.terms, change.new_constant) end -function modifyfunction(f::VAF, change::MOI.VectorConstantChange) +function modify_function(f::VAF, change::MOI.VectorConstantChange) return VAF(f.terms, change.new_constant) end -function modifyfunction(f::SQF, change::MOI.ScalarConstantChange) +function modify_function(f::SQF, change::MOI.ScalarConstantChange) return SQF(f.affine_terms, f.quadratic_terms, change.new_constant) end -function modifyfunction(f::VQF, change::MOI.VectorConstantChange) +function modify_function(f::VQF, change::MOI.VectorConstantChange) return VQF(f.affine_terms, f.quadratic_terms, change.new_constant) end function _modifycoefficient(terms::Vector{<:MOI.ScalarAffineTerm}, variable::VI, new_coefficient) @@ -518,11 +518,11 @@ function _modifycoefficient(terms::Vector{<:MOI.ScalarAffineTerm}, variable::VI, end terms end -function modifyfunction(f::SAF, change::MOI.ScalarCoefficientChange) +function modify_function(f::SAF, change::MOI.ScalarCoefficientChange) lin = _modifycoefficient(f.terms, change.variable, change.new_coefficient) return SAF(lin, f.constant) end -function modifyfunction(f::SQF, change::MOI.ScalarCoefficientChange) +function modify_function(f::SQF, change::MOI.ScalarCoefficientChange) lin = _modifycoefficient(f.affine_terms, change.variable, change.new_coefficient) return SQF(lin, f.quadratic_terms, f.constant) end @@ -553,13 +553,13 @@ function _modifycoefficients(n, terms::Vector{<:MOI.VectorAffineTerm}, variable: end terms end -function modifyfunction(f::VAF, change::MOI.MultirowChange) +function modify_function(f::VAF, change::MOI.MultirowChange) dim = MOI.output_dimension(f) coefficients = change.new_coefficients lin = _modifycoefficients(dim, f.terms, change.variable, coefficients) VAF(lin, f.constants) end -function modifyfunction(f::VQF, change::MOI.MultirowChange) +function modify_function(f::VQF, change::MOI.MultirowChange) dim = MOI.output_dimension(f) coefficients = change.new_coefficients lin = _modifycoefficients(dim, f.affine_terms, change.variable, coefficients) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 5fb31004cc..47ff2803b1 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -49,7 +49,7 @@ 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) -_modifyconstr(ci::CI{F, S}, f::F, s::S, change::MOI.AbstractFunctionModification) where {F, S} = (ci, modifyfunction(f, change), s) +_modifyconstr(ci::CI{F, S}, f::F, s::S, change::MOI.AbstractFunctionModification) where {F, S} = (ci, modify_function(f, change), s) function _modify(constrs::Vector{ConstraintEntry{F, S}}, ci::CI{F}, i::Int, change) where {F, S} constrs[i] = _modifyconstr(constrs[i]..., change) @@ -373,7 +373,7 @@ function MOI.set(model::AbstractModel, ::MOI.ObjectiveFunction, f::MOI.AbstractF end function MOI.modify(model::AbstractModel, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) - model.objective = modifyfunction(model.objective, change) + model.objective = modify_function(model.objective, change) end MOI.get(::AbstractModel, ::MOI.ListOfOptimizerAttributesSet) = MOI.AbstractOptimizerAttribute[] diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index a6662305bc..2d17de6734 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -20,7 +20,7 @@ function get_fallback(model::MOI.ModelLike, ::MOI.ObjectiveValue) F = MOI.get(model, MOI.ObjectiveFunctionType()) f = MOI.get(model, MOI.ObjectiveFunction{F}()) # TODO do not include constant if primal solution is a ray - return evalvariables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) + return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) end function constraint_constant(model::MOI.ModelLike, @@ -149,7 +149,7 @@ function get_fallback(model::MOI.ModelLike, ::MOI.ConstraintPrimal, idx::MOI.ConstraintIndex) f = MOI.get(model, MOI.ConstraintFunction(), idx) # TODO do not include constant if primal solution is a ray - return evalvariables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) + return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) end ################ Constraint Dual for Variable-wise constraints ################# diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index 4338fc1c6d..e135186ddc 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -370,7 +370,7 @@ function MOI.modify(uf::UniversalFallback, ci::CI{F, S}, change::MOI.AbstractFun MOI.modify(uf.model, ci, change) else (f, s) = uf.constraints[(F, S)][ci] - uf.constraints[(F, S)][ci] = (modifyfunction(f, change), s) + uf.constraints[(F, S)][ci] = (modify_function(f, change), s) end end diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index b83e3040f0..116c720efe 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -75,35 +75,35 @@ end @test MOI.VectorQuadraticTerm(Int32(3), scalarquad) === MOI.VectorQuadraticTerm(Int64(3), scalarquad) @test MOI.VectorQuadraticTerm{Float64}(Int32(3), scalarquad) === MOI.VectorQuadraticTerm(Int64(3), scalarquad) end -@testset "evalvariables" begin +@testset "eval_variables" begin # We do tests twice to make sure the function is not modified vals = Dict(w=>0, x=>3, y=>1, z=>5) fsv = MOI.SingleVariable(z) @test MOI.output_dimension(fsv) == 1 - @test MOIU.evalvariables(vi -> vals[vi], fsv) ≈ 5 - @test MOIU.evalvariables(vi -> vals[vi], fsv) ≈ 5 + @test MOIU.eval_variables(vi -> vals[vi], fsv) ≈ 5 + @test MOIU.eval_variables(vi -> vals[vi], fsv) ≈ 5 fvv = MOI.VectorOfVariables([x, z, y]) @test MOI.output_dimension(fvv) == 3 - @test MOIU.evalvariables(vi -> vals[vi], fvv) ≈ [3, 5, 1] - @test MOIU.evalvariables(vi -> vals[vi], fvv) ≈ [3, 5, 1] + @test MOIU.eval_variables(vi -> vals[vi], fvv) ≈ [3, 5, 1] + @test MOIU.eval_variables(vi -> vals[vi], fvv) ≈ [3, 5, 1] fsa = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x), MOI.ScalarAffineTerm(3.0, z), MOI.ScalarAffineTerm(2.0, y)], 2.0) @test MOI.output_dimension(fsa) == 1 - @test MOIU.evalvariables(vi -> vals[vi], fsa) ≈ 22 - @test MOIU.evalvariables(vi -> vals[vi], fsa) ≈ 22 + @test MOIU.eval_variables(vi -> vals[vi], fsa) ≈ 22 + @test MOIU.eval_variables(vi -> vals[vi], fsa) ≈ 22 fva = MOI.VectorAffineFunction(MOI.VectorAffineTerm.([2, 1, 2], MOI.ScalarAffineTerm.([1.0, 3.0, 2.0], [x, z, y])), [-3.0, 2.0]) @test MOI.output_dimension(fva) == 2 - @test MOIU.evalvariables(vi -> vals[vi], fva) ≈ [12, 7] - @test MOIU.evalvariables(vi -> vals[vi], fva) ≈ [12, 7] + @test MOIU.eval_variables(vi -> vals[vi], fva) ≈ [12, 7] + @test MOIU.eval_variables(vi -> vals[vi], fva) ≈ [12, 7] fsq = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), MOI.ScalarQuadraticTerm.(1.0, [x, w, w], [z, z, y]), -3.0) @test MOI.output_dimension(fsq) == 1 - @test MOIU.evalvariables(vi -> vals[vi], fsq) ≈ 16 - @test MOIU.evalvariables(vi -> vals[vi], fsq) ≈ 16 + @test MOIU.eval_variables(vi -> vals[vi], fsq) ≈ 16 + @test MOIU.eval_variables(vi -> vals[vi], fsq) ≈ 16 fvq = MOI.VectorQuadraticFunction(MOI.VectorAffineTerm.([2, 1], MOI.ScalarAffineTerm.(1.0, [x, y])), MOI.VectorQuadraticTerm.([1, 2, 2], MOI.ScalarQuadraticTerm.(1.0, [x, w, w], [z, z, y])), [-3.0, -2.0]) @test MOI.output_dimension(fvq) == 2 - @test MOIU.evalvariables(vi -> vals[vi], fvq) ≈ [13, 1] - @test MOIU.evalvariables(vi -> vals[vi], fvq) ≈ [13, 1] + @test MOIU.eval_variables(vi -> vals[vi], fvq) ≈ [13, 1] + @test MOIU.eval_variables(vi -> vals[vi], fvq) ≈ [13, 1] end @testset "mapvariables" begin fsq = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), @@ -285,15 +285,15 @@ end @test f ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([3, 4], [x, y]), 5) - MOI.SingleVariable(x) end @testset "modification" begin - f = MOIU.modifyfunction(f, MOI.ScalarConstantChange(6)) + f = MOIU.modify_function(f, MOI.ScalarConstantChange(6)) @test f.constant == 6 g = deepcopy(f) @test g ≈ f - f = MOIU.modifyfunction(f, MOI.ScalarCoefficientChange(y, 3)) + f = MOIU.modify_function(f, MOI.ScalarCoefficientChange(y, 3)) @test !(g ≈ f) @test g.terms == MOI.ScalarAffineTerm.([2, 4], [x, y]) @test f.terms == MOI.ScalarAffineTerm.([2, 3], [x, y]) - f = MOIU.modifyfunction(f, MOI.ScalarCoefficientChange(x, 0)) + f = MOIU.modify_function(f, MOI.ScalarCoefficientChange(x, 0)) @test f.terms == MOI.ScalarAffineTerm.([3], [y]) end end @@ -409,13 +409,13 @@ end MOI.ScalarQuadraticTerm.([1.0, 2.0, 3.0], [x, y, x], [x, y, y]), 7.0) / 2.0 end @testset "modification" begin - f = MOIU.modifyfunction(f, MOI.ScalarConstantChange(9)) + f = MOIU.modify_function(f, MOI.ScalarConstantChange(9)) @test f.constant == 9 - f = MOIU.modifyfunction(f, MOI.ScalarCoefficientChange(y, 0)) + f = MOIU.modify_function(f, MOI.ScalarCoefficientChange(y, 0)) @test f.affine_terms == MOI.ScalarAffineTerm.([3], [x]) g = deepcopy(f) @test f ≈ g - f = MOIU.modifyfunction(f, MOI.ScalarCoefficientChange(y, 2)) + f = MOIU.modify_function(f, MOI.ScalarCoefficientChange(y, 2)) @test !(f ≈ g) @test g.affine_terms == MOI.ScalarAffineTerm.([3], [x]) @test f.affine_terms == MOI.ScalarAffineTerm.([3, 2], [x, y]) @@ -430,26 +430,26 @@ end @test MOI.output_dimension(f) == 2 @test f.terms == MOI.VectorAffineTerm.([1, 1, 2], MOI.ScalarAffineTerm.([2, 4, 3], [x, y, y])) @test MOIU.constant_vector(f) == [5, 7] - f = MOIU.modifyfunction(f, MOI.VectorConstantChange([6, 8])) + f = MOIU.modify_function(f, MOI.VectorConstantChange([6, 8])) @test MOIU.constant_vector(f) == [6, 8] g = deepcopy(f) @test f ≈ g - f = MOIU.modifyfunction(f, MOI.MultirowChange(y, [(2, 9)])) + f = MOIU.modify_function(f, MOI.MultirowChange(y, [(2, 9)])) @test !(f ≈ g) @test f.terms == MOI.VectorAffineTerm.([1, 1, 2], MOI.ScalarAffineTerm.([2, 4, 9], [x, y, y])) @test g.terms == MOI.VectorAffineTerm.([1, 1, 2], MOI.ScalarAffineTerm.([2, 4, 3], [x, y, y])) - f = MOIU.modifyfunction(f, MOI.MultirowChange(y, [(1, 0)])) + f = MOIU.modify_function(f, MOI.MultirowChange(y, [(1, 0)])) @test f.terms == MOI.VectorAffineTerm.([1, 2], MOI.ScalarAffineTerm.([2, 9], [x, y])) end @testset "Quadratic" begin f = MOI.VectorQuadraticFunction(MOI.VectorAffineTerm.([1, 2, 2], MOI.ScalarAffineTerm.([3, 1, 2], [x, x, y])), MOI.VectorQuadraticTerm.([1, 1, 2], MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y])), [7, 3, 4]) @test MOI.output_dimension(f) == 3 - f = MOIU.modifyfunction(f, MOI.VectorConstantChange([10, 11, 12])) + f = MOIU.modify_function(f, MOI.VectorConstantChange([10, 11, 12])) @test MOIU.constant_vector(f) == [10, 11, 12] - f = MOIU.modifyfunction(f, MOI.MultirowChange(y, [(2, 0), (1, 1)])) + f = MOIU.modify_function(f, MOI.MultirowChange(y, [(2, 0), (1, 1)])) @test f.affine_terms == MOI.VectorAffineTerm.([1, 2, 1], MOI.ScalarAffineTerm.([3, 1, 1], [x, x, y])) g = deepcopy(f) - f = MOIU.modifyfunction(f, MOI.MultirowChange(x, [(1, 0), (3, 4)])) + f = MOIU.modify_function(f, MOI.MultirowChange(x, [(1, 0), (3, 4)])) @test f.affine_terms == MOI.VectorAffineTerm.([2, 1, 3], MOI.ScalarAffineTerm.([1, 1, 4], [x, y, x])) @test g.affine_terms == MOI.VectorAffineTerm.([1, 2, 1], MOI.ScalarAffineTerm.([3, 1, 1], [x, x, y])) end diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index 78010f9a05..cb56f5e0e5 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -111,8 +111,8 @@ end @test (MOI.VectorQuadraticFunction{Int},MOI.PositiveSemidefiniteConeTriangle) in loc @test (MOI.VectorQuadraticFunction{Int},MOI.PositiveSemidefiniteConeTriangle) in loc - f3 = MOIU.modifyfunction(f1, MOI.ScalarConstantChange(9)) - f3 = MOIU.modifyfunction(f3, MOI.ScalarCoefficientChange(y, 2)) + f3 = MOIU.modify_function(f1, MOI.ScalarConstantChange(9)) + f3 = MOIU.modify_function(f3, MOI.ScalarCoefficientChange(y, 2)) @test !(MOI.get(model, MOI.ConstraintFunction(), c1) ≈ f3) MOI.set(model, MOI.ConstraintFunction(), c1, f3) From 2f255f2b79eeef29f1295d3e2922c25d2de1ab4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 18:21:29 -0400 Subject: [PATCH 07/12] iscanonical -> is_canonical --- docs/src/apireference.md | 2 +- src/Utilities/functions.jl | 4 ++-- test/Utilities/functions.jl | 26 +++++++++++++------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 6193ef7be1..63b04692b9 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -626,7 +626,7 @@ Utilities.modifyfunction The following functions can be used to canonicalize a function: ```@docs -Utilities.iscanonical +Utilities.is_canonical Utilities.canonical Utilities.canonicalize! ``` diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 04b0c50ca6..b44d010aa1 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -204,13 +204,13 @@ function unsafe_add(t1::VT, t2::VT) where VT <: Union{MOI.VectorAffineTerm, end """ - iscanonical(f::Union{ScalarAffineFunction, ScalarQuadraticFunction + is_canonical(f::Union{ScalarAffineFunction, ScalarQuadraticFunction VectorAffineFunction, VectorQuadraticTerm}) Returns a Bool indicating whether the function is in canonical form. See [`canonical`](@ref). """ -function iscanonical(f::Union{SAF, VAF, SQF, VQF}) +function is_canonical(f::Union{SAF, VAF, SQF, VQF}) is_strictly_sorted(f.terms, MOI.term_indices, t -> !iszero(MOI.coefficient(t))) end diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 116c720efe..e8690850e9 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -461,20 +461,20 @@ end (MOI.coefficient.(f1.terms) ≈ MOI.coefficient.(f2.terms))) end function test_canonicalization(f::T, expected::T) where {T <: Union{MOI.ScalarAffineFunction, MOI.VectorAffineFunction}} - @test MOIU.iscanonical(expected) + @test MOIU.is_canonical(expected) g = @inferred(MOIU.canonical(f)) @test isapprox_ordered(g, expected) - @test MOIU.iscanonical(g) - @test MOIU.iscanonical(expected) + @test MOIU.is_canonical(g) + @test MOIU.is_canonical(expected) @test isapprox_ordered(MOIU.canonical(g), g) @test MOIU.canonical(g) !== g @test @allocated(MOIU.canonicalize!(f)) == 0 end @testset "ScalarAffine" begin - @test MOIU.iscanonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(Float64[], MOI.VariableIndex.(Int[])), 1.0)) - @test !MOIU.iscanonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([0.0], MOI.VariableIndex.([1])), 1.0)) - @test !MOIU.iscanonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], MOI.VariableIndex.([2, 1])), 2.0)) - @test !MOIU.iscanonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 0.0], MOI.VariableIndex.([1, 2])), 2.0)) + @test MOIU.is_canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(Float64[], MOI.VariableIndex.(Int[])), 1.0)) + @test !MOIU.is_canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([0.0], MOI.VariableIndex.([1])), 1.0)) + @test !MOIU.is_canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], MOI.VariableIndex.([2, 1])), 2.0)) + @test !MOIU.is_canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 0.0], MOI.VariableIndex.([1, 2])), 2.0)) test_canonicalization( MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(Float64[], MOI.VariableIndex[]), 1.5), @@ -526,12 +526,12 @@ end ) end @testset "VectorAffine" begin - @test MOIU.iscanonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.(Int[], MOI.ScalarAffineTerm.(Float64[], MOI.VariableIndex.(Int[]))), Float64[])) - @test !MOIU.iscanonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1], MOI.ScalarAffineTerm.([0.0], MOI.VariableIndex.([1]))), [1.0])) - @test !MOIU.iscanonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 2], MOI.ScalarAffineTerm.([0.0, 1.0], MOI.VariableIndex.([1, 2]))), [1.0])) - @test !MOIU.iscanonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([2, 1], MOI.ScalarAffineTerm.([1.0, 1.0], MOI.VariableIndex.([1, 2]))), [1.0])) - @test !MOIU.iscanonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 1], MOI.ScalarAffineTerm.([1.0, -1.0], MOI.VariableIndex.([1, 1]))), [1.0])) - @test !MOIU.iscanonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 1], MOI.ScalarAffineTerm.([1.0, 1.0], MOI.VariableIndex.([1, 1]))), [1.0])) + @test MOIU.is_canonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.(Int[], MOI.ScalarAffineTerm.(Float64[], MOI.VariableIndex.(Int[]))), Float64[])) + @test !MOIU.is_canonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1], MOI.ScalarAffineTerm.([0.0], MOI.VariableIndex.([1]))), [1.0])) + @test !MOIU.is_canonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 2], MOI.ScalarAffineTerm.([0.0, 1.0], MOI.VariableIndex.([1, 2]))), [1.0])) + @test !MOIU.is_canonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([2, 1], MOI.ScalarAffineTerm.([1.0, 1.0], MOI.VariableIndex.([1, 2]))), [1.0])) + @test !MOIU.is_canonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 1], MOI.ScalarAffineTerm.([1.0, -1.0], MOI.VariableIndex.([1, 1]))), [1.0])) + @test !MOIU.is_canonical(MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 1], MOI.ScalarAffineTerm.([1.0, 1.0], MOI.VariableIndex.([1, 1]))), [1.0])) test_canonicalization( MOI.VectorAffineFunction(MOI.VectorAffineTerm.(Int[], MOI.ScalarAffineTerm.(Float64[], MOI.VariableIndex.(Int[]))), Float64[]), From 53d0d7c951363b66f53344ff1d47adc602126f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 18:23:22 -0400 Subject: [PATCH 08/12] Defined removing a variable --- src/indextypes.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/indextypes.jl b/src/indextypes.jl index 0a4560e909..9e82465f24 100644 --- a/src/indextypes.jl +++ b/src/indextypes.jl @@ -99,8 +99,8 @@ Delete the referenced object from the model. Throw [`DeleteNotAllowed`](@ref) if if `index` cannot be deleted. The following modifications also take effect if `Index` is [`VariableIndex`](@ref): -* If `index` used in the objective function, it is removed from the function; - see [`Utilities.remove_variable`](@ref). +* If `index` used in the objective function, it is removed from the function, + i.e., it is substituted for zero. * For each `func`-in-`set` constraint of the model: - If `func isa SingleVariable` and `func.variable == index` then the constraint is deleted. @@ -110,8 +110,8 @@ The following modifications also take effect if `Index` is [`VariableIndex`](@re then the variable is removed from `func` and `set` is replaced by `update_dimension(set, MOI.dimension(set) - 1)`. * Otherwise, a [`DeleteNotAllowed`](@ref) error is thrown. - - Otherwise, the variable is removed from `func`; see - [`Utilities.remove_variable`](@ref). + - Otherwise, the variable is removed from `func`, i.e., it is substituted for + zero. """ delete(model::ModelLike, index::Index) = throw(DeleteNotAllowed(index)) From e4665bf438b2e4abba1938262ca09bf16f74cf07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 14 Jul 2019 19:06:56 -0400 Subject: [PATCH 09/12] Fix --- docs/src/apireference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 63b04692b9..149d236b14 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -621,7 +621,7 @@ Utilities.remove_variable Utilities.all_coefficients Utilities.unsafe_add Utilities.isapprox_zero -Utilities.modifyfunction +Utilities.modify_function ``` The following functions can be used to canonicalize a function: From b44d3c8f392e2f04fe2f324ea764f90c11eb7175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 16 Jul 2019 22:09:50 -0600 Subject: [PATCH 10/12] got -> queried --- src/Utilities/universalfallback.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index e135186ddc..2f9d55bb5a 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -286,7 +286,7 @@ function MOI.get(uf::UniversalFallback, ::Type{CI{F, S}}, name::String) where {F if MOI.supports_constraint(uf.model, F, S) ci = MOI.get(uf.model, CI{F, S}, name) else - # There is no `F`-in-`S` constraint in `b.model`, `ci` is only got + # There is no `F`-in-`S` constraint in `b.model`, `ci` is only queried # to check for duplicate names. ci = MOI.get(uf.model, CI, name) end From 6da195df9d9b4a57f3cadbcd0d882afda84ea078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 16 Jul 2019 22:12:28 -0600 Subject: [PATCH 11/12] Add missing delete --- src/Utilities/model.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 47ff2803b1..70682cc093 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -194,6 +194,8 @@ function MOI.delete(model::AbstractModel{T}, vi::VI) where T delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{T}}(vi.value)) delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer}(vi.value)) delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.ZeroOne}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{T}}(vi.value)) + delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{T}}(vi.value)) end function MOI.delete(model::AbstractModel, vis::Vector{VI}) # Delete `VectorOfVariables(vis)` constraints as otherwise, it will error @@ -400,6 +402,8 @@ single_variable_flag(::Type{MOI.Integer}) = 0x10 single_variable_flag(::Type{MOI.ZeroOne}) = 0x20 single_variable_flag(::Type{<:MOI.Semicontinuous}) = 0x40 single_variable_flag(::Type{<:MOI.Semiinteger}) = 0x80 +# If a set is added here, a line should be added in +# `MOI.delete(::AbstractModel, ::MOI.VariableIndex)` function flag_to_set_type(flag::UInt8, T::Type) if flag == 0x1 From b54ae5deb5b3875e07685df8f58c26cae07a9d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 16 Jul 2019 22:22:03 -0600 Subject: [PATCH 12/12] Move dimension update to MOI --- docs/src/apireference.md | 4 ++-- src/Test/modellike.jl | 32 ++++++++++++++++++++++++++++++ src/Utilities/model.jl | 4 ++-- src/Utilities/sets.jl | 31 ----------------------------- src/Utilities/universalfallback.jl | 2 +- src/sets.jl | 32 ++++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 36 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 149d236b14..f6a069f230 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -254,6 +254,8 @@ Functions for getting properties of sets. ```@docs dimension constant(s::EqualTo) +supports_dimension_update +update_dimension ``` ### Scalar sets @@ -644,8 +646,6 @@ Utilities.vectorize The following utilities are available for sets: ```@docs -Utilities.supports_dimension_update -Utilities.update_dimension Utilities.shift_constant ``` diff --git a/src/Test/modellike.jl b/src/Test/modellike.jl index c54b5947b3..7c4853df2e 100644 --- a/src/Test/modellike.jl +++ b/src/Test/modellike.jl @@ -488,6 +488,13 @@ function delete_test(model::MOI.ModelLike) @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y) @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(4) + @test Set(MOI.get(model, MOI.ListOfConstraints())) == + Set([(MOI.SingleVariable, MOI.GreaterThan{Float64}), + (MOI.VectorOfVariables, MOI.Nonpositives)]) + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == [cx] + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy] MOI.delete(model, y[3]) @test MOI.is_valid(model, x) @test MOI.is_valid(model, y[1]) @@ -500,6 +507,13 @@ function delete_test(model::MOI.ModelLike) @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[1, 2, 4]]) @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(3) + @test Set(MOI.get(model, MOI.ListOfConstraints())) == + Set([(MOI.SingleVariable, MOI.GreaterThan{Float64}), + (MOI.VectorOfVariables, MOI.Nonpositives)]) + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == [cx] + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy] MOI.delete(model, y[1]) @test MOI.is_valid(model, x) @test !MOI.is_valid(model, y[1]) @@ -512,6 +526,13 @@ function delete_test(model::MOI.ModelLike) @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0) @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) + @test Set(MOI.get(model, MOI.ListOfConstraints())) == + Set([(MOI.SingleVariable, MOI.GreaterThan{Float64}), + (MOI.VectorOfVariables, MOI.Nonpositives)]) + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == [cx] + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy] MOI.delete(model, x) @test !MOI.is_valid(model, x) @test !MOI.is_valid(model, y[1]) @@ -522,6 +543,12 @@ function delete_test(model::MOI.ModelLike) @test MOI.is_valid(model, cy) @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]]) @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2) + @test MOI.get(model, MOI.ListOfConstraints()) == + [(MOI.VectorOfVariables, MOI.Nonpositives)] + @test isempty(MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.SingleVariable, MOI.GreaterThan{Float64}}())) + @test MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy] MOI.delete(model, y[[2, 4]]) @test !MOI.is_valid(model, x) @test !MOI.is_valid(model, y[1]) @@ -530,4 +557,9 @@ function delete_test(model::MOI.ModelLike) @test !MOI.is_valid(model, y[4]) @test !MOI.is_valid(model, cx) @test !MOI.is_valid(model, cy) + @test isempty(MOI.get(model, MOI.ListOfConstraints())) + @test isempty(MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.SingleVariable, MOI.GreaterThan{Float64}}())) + @test isempty(MOI.get(model, MOI.ListOfConstraintIndices{ + MOI.VectorOfVariables, MOI.Nonpositives}())) end diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 70682cc093..165ebe50be 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -115,7 +115,7 @@ remove_variable(f, s, vi::VI) = remove_variable(f, vi), s function remove_variable(f::MOI.VectorOfVariables, s, vi::VI) g = remove_variable(f, vi) if length(g.variables) != length(f.variables) - t = update_dimension(s, length(g.variables)) + t = MOI.update_dimension(s, length(g.variables)) else t = s end @@ -144,7 +144,7 @@ function _vector_of_variables_with( if length(f.variables) > 1 # If `supports_dimension_update(s)` then the variable will be # removed in `_remove_variable`. - if !supports_dimension_update(typeof(s)) + if !MOI.supports_dimension_update(typeof(s)) throw_delete_variable_in_vov(vi) end else diff --git a/src/Utilities/sets.jl b/src/Utilities/sets.jl index aa6e178def..f23ed8c604 100644 --- a/src/Utilities/sets.jl +++ b/src/Utilities/sets.jl @@ -1,34 +1,3 @@ -""" - supports_dimension_update(S::Type{<:MOI.AbstractVectorSet}) - -Return a `Bool` indicating whether the elimination of any dimension of -`n`-dimensional sets of type `S` give an `n-1`-dimensional set `S`. -By default, this function returns `false` so it should only be implemented -for sets that supports dimension update. - -For instance, `supports_dimension_update(MOI.Nonnegatives}` is `true` because -the elimination of any dimension of the `n`-dimensional nonnegative orthant -gives the `n-1`-dimensional nonnegative orthant. However -`supports_dimension_update(MOI.ExponentialCone}` is `false`. -""" -function supports_dimension_update(::Type{<:MOI.AbstractVectorSet}) - return false -end -function supports_dimension_update(::Type{<:Union{ - MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}}) - return true -end - -""" - update_dimension(s::AbstractVectorSet, new_dim) - -Returns a set with the dimension modified to `new_dim`. -""" -function update_dimension(set::Union{ - MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}, new_dim) - return typeof(set)(new_dim) -end - """ shift_constant(set::MOI.AbstractScalarSet, offset) diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index 2f9d55bb5a..c548e92a58 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -99,7 +99,7 @@ function _remove_variable(uf::UniversalFallback, f::MOI.VectorOfVariables, s = constraint if vi in f.variables if length(f.variables) > 1 - if supports_dimension_update(S) + if MOI.supports_dimension_update(S) constraints[ci] = remove_variable(f, s, vi) else throw_delete_variable_in_vov(vi) diff --git a/src/sets.jl b/src/sets.jl index b785106ed8..61e2b73681 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -591,3 +591,35 @@ function Base.copy(set::Union{Reals, Zeros, Nonnegatives, Nonpositives, return set end Base.copy(set::S) where {S <: Union{SOS1, SOS2}} = S(copy(set.weights)) + +""" + supports_dimension_update(S::Type{<:MOI.AbstractVectorSet}) + +Return a `Bool` indicating whether the elimination of any dimension of +`n`-dimensional sets of type `S` give an `n-1`-dimensional set `S`. +By default, this function returns `false` so it should only be implemented +for sets that supports dimension update. + +For instance, `supports_dimension_update(MOI.Nonnegatives}` is `true` because +the elimination of any dimension of the `n`-dimensional nonnegative orthant +gives the `n-1`-dimensional nonnegative orthant. However +`supports_dimension_update(MOI.ExponentialCone}` is `false`. +""" +function supports_dimension_update(::Type{<:AbstractVectorSet}) + return false +end +function supports_dimension_update(::Type{<:Union{ + Reals, Zeros, Nonnegatives, Nonpositives}}) + return true +end + +""" + update_dimension(s::AbstractVectorSet, new_dim) + +Returns a set with the dimension modified to `new_dim`. +""" +function update_dimension end +function update_dimension(set::Union{ + Reals, Zeros, Nonnegatives, Nonpositives}, new_dim) + return typeof(set)(new_dim) +end