From 0197fb33d0114980b2e85b6671c12e5fb13466bd Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 15 Jul 2021 14:41:05 +1200 Subject: [PATCH 01/12] WIP: refactoring of Box.jl --- src/Utilities/box.jl | 156 ++++++++++++++++++++++++++++++---------- src/Utilities/model.jl | 73 +------------------ test/Utilities/box.jl | 103 ++++++++++++++++++++++++++ test/Utilities/model.jl | 8 --- 4 files changed, 225 insertions(+), 115 deletions(-) create mode 100644 test/Utilities/box.jl diff --git a/src/Utilities/box.jl b/src/Utilities/box.jl index b5915a05d4..802e22d09f 100644 --- a/src/Utilities/box.jl +++ b/src/Utilities/box.jl @@ -1,23 +1,9 @@ -# Sets setting lower bound: -extract_lower_bound(set::MOI.EqualTo) = set.value -function extract_lower_bound( - set::Union{MOI.GreaterThan,MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}, -) - return set.lower -end -# 0xcb = 0x80 | 0x40 | 0x8 | 0x2 | 0x1 -const LOWER_BOUND_MASK = 0xcb - -# Sets setting upper bound: -extract_upper_bound(set::MOI.EqualTo) = set.value -function extract_upper_bound( - set::Union{MOI.LessThan,MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}, -) - return set.upper -end -# 0xcd = 0x80 | 0x40 | 0x8 | 0x4 | 0x1 -const UPPER_BOUND_MASK = 0xcd +""" + SUPPORTED_VARIABLE_SCALAR_SETS{T} +The union of scalar sets for `SingleVariable` constraints supported by +`Utilities.Box` (and therefore in `Utilities.Model`). +""" const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{ MOI.EqualTo{T}, MOI.GreaterThan{T}, @@ -29,6 +15,95 @@ const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{ MOI.Semiinteger{T}, } +# 0xcb = 0x80 | 0x40 | 0x8 | 0x2 | 0x1 +const LOWER_BOUND_MASK = 0xcb +# 0xcd = 0x80 | 0x40 | 0x8 | 0x4 | 0x1 +const UPPER_BOUND_MASK = 0xcd + +single_variable_flag(::Type{<:MOI.EqualTo}) = 0x1 +single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x2 +single_variable_flag(::Type{<:MOI.LessThan}) = 0x4 +single_variable_flag(::Type{<:MOI.Interval}) = 0x8 +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, ::Type{T}) where {T} + if flag == 0x1 + return MOI.EqualTo{T} + elseif flag == 0x2 + return MOI.GreaterThan{T} + elseif flag == 0x4 + return MOI.LessThan{T} + elseif flag == 0x8 + return MOI.Interval{T} + elseif flag == 0x10 + return MOI.Integer + elseif flag == 0x20 + return MOI.ZeroOne + elseif flag == 0x40 + return MOI.Semicontinuous{T} + else + @assert flag == 0x80 + return MOI.Semiinteger{T} + end +end + +# Julia doesn't infer `S1` correctly, so we use a function barrier to improve +# inference. +function _throw_if_lower_bound_set(variable, S2, mask, T) + S1 = flag_to_set_type(mask, T) + throw(MOI.LowerBoundAlreadySet{S1,S2}(variable)) + return +end + +function throw_if_lower_bound_set(variable, S2, mask, T) + lower_mask = mask & LOWER_BOUND_MASK + if iszero(lower_mask) + return # No lower bound set. + elseif iszero(single_variable_flag(S2) & LOWER_BOUND_MASK) + return # S2 isn't related to the lower bound. + end + return _throw_if_lower_bound_set(variable, S2, lower_mask, T) +end + +# Julia doesn't infer `S1` correctly, so we use a function barrier to improve +# inference. +function _throw_if_upper_bound_set(variable, S2, mask, T) + S1 = flag_to_set_type(mask, T) + throw(MOI.UpperBoundAlreadySet{S1,S2}(variable)) + return +end + +function throw_if_upper_bound_set(variable, S2, mask, T) + upper_mask = mask & UPPER_BOUND_MASK + if iszero(upper_mask) + return # No upper bound set. + elseif iszero(single_variable_flag(S2) & UPPER_BOUND_MASK) + return # S2 isn't related to the upper bound. + end + return _throw_if_upper_bound_set(variable, S2, upper_mask, T) +end + +function _lower_bound( + set::Union{MOI.GreaterThan,MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}, +) + return set.lower +end + +_lower_bound(set::MOI.EqualTo) = set.value + +function _upper_bound( + set::Union{MOI.LessThan,MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}, +) + return set.upper +end + +_upper_bound(set::MOI.EqualTo) = set.value + """ struct Box{T} lower::Vector{T} @@ -59,12 +134,6 @@ function Base.resize!(b::Box, n) return end -function add_free(b::Box{T}) where {T} - push!(b.lower, _no_lower_bound(T)) - push!(b.upper, _no_upper_bound(T)) - return -end - # Use `-Inf` and `Inf` for `AbstractFloat` subtypes. _no_lower_bound(::Type{T}) where {T} = zero(T) _no_lower_bound(::Type{T}) where {T<:AbstractFloat} = typemin(T) @@ -80,31 +149,22 @@ function load_constants( if iszero(flag & LOWER_BOUND_MASK) b.lower[offset+1] = _no_lower_bound(T) else - b.lower[offset+1] = extract_lower_bound(set) + b.lower[offset+1] = _lower_bound(set) end if iszero(flag & UPPER_BOUND_MASK) b.upper[offset+1] = _no_upper_bound(T) else - b.upper[offset+1] = extract_upper_bound(set) + b.upper[offset+1] = _upper_bound(set) end return end -function merge_bounds(b::Box, index, set) - flag = single_variable_flag(typeof(set)) - if !iszero(flag & LOWER_BOUND_MASK) - b.lower[index] = extract_lower_bound(set) - end - if !iszero(flag & UPPER_BOUND_MASK) - b.upper[index] = extract_upper_bound(set) - end -end - function_constants(::Box{T}, row) where {T} = zero(T) function set_from_constants(b::Box, ::Type{<:MOI.EqualTo}, index) return MOI.EqualTo(b.lower[index]) end + function set_from_constants( b::Box, S::Type{<:Union{MOI.GreaterThan,MOI.EqualTo}}, @@ -113,9 +173,11 @@ function set_from_constants( # Lower and upper bounds are equal for `EqualTo`, we can take either of them. return S(b.lower[index]) end + function set_from_constants(b::Box, S::Type{<:MOI.LessThan}, index) return S(b.upper[index]) end + function set_from_constants( b::Box, S::Type{<:Union{MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}}, @@ -123,6 +185,7 @@ function set_from_constants( ) return S(b.lower[index], b.upper[index]) end + function set_from_constants( ::Box, S::Type{<:Union{MOI.Integer,MOI.ZeroOne}}, @@ -130,3 +193,22 @@ function set_from_constants( ) return S() end + +# Function used in MOI.Utilities.AbstractModel. + +function _merge_bounds(b::Box, index, set) + flag = single_variable_flag(typeof(set)) + if !iszero(flag & LOWER_BOUND_MASK) + b.lower[index] = _lower_bound(set) + end + if !iszero(flag & UPPER_BOUND_MASK) + b.upper[index] = _upper_bound(set) + end + return +end + +function _add_free(b::Box{T}) where {T} + push!(b.lower, _no_lower_bound(T)) + push!(b.upper, _no_upper_bound(T)) + return +end diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 1fad5751e1..6c2219d443 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -28,7 +28,7 @@ function _add_variables(::Nothing, ::Int64) end function MOI.add_variable(model::AbstractModel{T}) where {T} vi = VI(model.num_variables_created += 1) push!(model.single_variable_mask, 0x0) - add_free(model.variable_bounds) + __add_free(model.variable_bounds) if model.variable_indices !== nothing push!(model.variable_indices, vi) end @@ -410,73 +410,6 @@ function MOI.get( end # Constraints -single_variable_flag(::Type{<:MOI.EqualTo}) = 0x1 -single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x2 -single_variable_flag(::Type{<:MOI.LessThan}) = 0x4 -single_variable_flag(::Type{<:MOI.Interval}) = 0x8 -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, ::Type{T}) where {T} - if flag == 0x1 - return MOI.EqualTo{T} - elseif flag == 0x2 - return MOI.GreaterThan{T} - elseif flag == 0x4 - return MOI.LessThan{T} - elseif flag == 0x8 - return MOI.Interval{T} - elseif flag == 0x10 - return MOI.Integer - elseif flag == 0x20 - return MOI.ZeroOne - elseif flag == 0x40 - return MOI.Semicontinuous{T} - else - @assert flag == 0x80 - return MOI.Semiinteger{T} - end -end - -# Julia doesn't infer `S1` correctly, so we use a function barrier to improve -# inference. -function _throw_if_lower_bound_set(variable, S2, mask, T) - S1 = flag_to_set_type(mask, T) - throw(MOI.LowerBoundAlreadySet{S1,S2}(variable)) - return -end - -function throw_if_lower_bound_set(variable, S2, mask, T) - lower_mask = mask & LOWER_BOUND_MASK - if iszero(lower_mask) - return # No lower bound set. - elseif iszero(single_variable_flag(S2) & LOWER_BOUND_MASK) - return # S2 isn't related to the lower bound. - end - return _throw_if_lower_bound_set(variable, S2, lower_mask, T) -end - -# Julia doesn't infer `S1` correctly, so we use a function barrier to improve -# inference. -function _throw_if_upper_bound_set(variable, S2, mask, T) - S1 = flag_to_set_type(mask, T) - throw(MOI.UpperBoundAlreadySet{S1,S2}(variable)) - return -end - -function throw_if_upper_bound_set(variable, S2, mask, T) - upper_mask = mask & UPPER_BOUND_MASK - if iszero(upper_mask) - return # No upper bound set. - elseif iszero(single_variable_flag(S2) & UPPER_BOUND_MASK) - return # S2 isn't related to the upper bound. - end - return _throw_if_upper_bound_set(variable, S2, upper_mask, T) -end function MOI.supports_constraint( ::AbstractModel{T}, @@ -504,7 +437,7 @@ function MOI.add_constraint( throw_if_lower_bound_set(f.variable, typeof(s), mask, T) throw_if_upper_bound_set(f.variable, typeof(s), mask, T) # No error should be thrown now, we can modify `model`. - merge_bounds(model.variable_bounds, index, s) + _merge_bounds(model.variable_bounds, index, s) model.single_variable_mask[index] = mask | flag return CI{MOI.SingleVariable,typeof(s)}(index) end @@ -576,7 +509,7 @@ function MOI.set( set::S, ) where {T,S<:SUPPORTED_VARIABLE_SCALAR_SETS{T}} MOI.throw_if_not_valid(model, ci) - merge_bounds(model.variable_bounds, ci.value, set) + _merge_bounds(model.variable_bounds, ci.value, set) return end diff --git a/test/Utilities/box.jl b/test/Utilities/box.jl new file mode 100644 index 0000000000..0475b2e965 --- /dev/null +++ b/test/Utilities/box.jl @@ -0,0 +1,103 @@ +module TestBox + +using Test +import MathOptInterface +const MOI = MathOptInterface + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +function test_flag_to_set_type() + T = Int + @test_throws AssertionError MOI.Utilities.flag_to_set_type(0x11, T) + @test MOI.Utilities.flag_to_set_type(0x10, T) == MOI.Integer + @test MOI.Utilities.flag_to_set_type(0x20, T) == MOI.ZeroOne + return +end + +function test_equal() + a = MOI.Utilities.Box([1, 2], [3, 4]) + b = MOI.Utilities.Box([1.0, 2.0], [3.0, 4.0]) + c = MOI.Utilities.Box([1.0, 2.0], [3.0, 5.0]) + @test a == a + @test a == b + @test a != c + return +end + +function test_empty() + a = MOI.Utilities.Box([1, 2], [3, 4]) + empty!(a) + @test a == MOI.Utilities.Box{Int}() + return +end + +function test_resize() + a = MOI.Utilities.Box([1, 2], [3, 4]) + @test length(a.lower) == 2 + @test length(a.upper) == 2 + resize!(a, 4) + @test length(a.lower) == 4 + @test length(a.upper) == 4 + return +end + +function test_load_constants() + a = MOI.Utilities.Box([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) + MOI.Utilities.load_constants(a, 0, MOI.Interval(1.0, 2.0)) + MOI.Utilities.load_constants(a, 1, MOI.GreaterThan(3.0)) + MOI.Utilities.load_constants(a, 2, MOI.LessThan(4.0)) + @test a == MOI.Utilities.Box([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + return +end + +function test_function_constants() + a = MOI.Utilities.Box([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) + MOI.Utilities.function_constants(a, 0) == 0.0 + return +end + +function test_set_from_constants() + a = MOI.Utilities.Box([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + @test MOI.Utilities.set_from_constants(a, MOI.Interval{Float64}, 1) == + MOI.Interval(1.0, 2.0) + @test MOI.Utilities.set_from_constants(a, MOI.GreaterThan{Float64}, 2) == + MOI.GreaterThan(3.0) + @test MOI.Utilities.set_from_constants(a, MOI.LessThan{Float64}, 3) == + MOI.LessThan(4.0) + @test MOI.Utilities.set_from_constants(a, MOI.ZeroOne, 2) == MOI.ZeroOne() + return +end + +function test_merge_bounds() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_free(a) + @test a == MOI.Utilities.Box{Int}(Int[0], Int[0]) + MOI.Utilities.load_constants(a, 0, MOI.GreaterThan(3)) + @test a == MOI.Utilities.Box{Int}(Int[3], Int[0]) + MOI.Utilities._merge_bounds(a, 1, MOI.LessThan(4)) + @test a == MOI.Utilities.Box{Int}(Int[3], Int[4]) + return +end + +function test_add_free() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_free(a) + @test a == MOI.Utilities.Box{Int}(Int[0], Int[0]) + a = MOI.Utilities.Box{Float64}() + MOI.Utilities._add_free(a) + @test a == MOI.Utilities.Box{Float64}(Float64[-Inf], Float64[Inf]) + return +end + +end # module + +TestBox.runtests() diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index eeb7532c0f..e92e0e56ce 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -162,14 +162,6 @@ function test_TestExternalModel() return end -function test_flag_to_set_type() - T = Int - @test_throws AssertionError MOI.Utilities.flag_to_set_type(0x11, T) - @test MOI.Utilities.flag_to_set_type(0x10, T) == MOI.Integer - @test MOI.Utilities.flag_to_set_type(0x20, T) == MOI.ZeroOne - return -end - function test_bound_twice() for T in [Int, Float64] model = MOI.Utilities.Model{T}() From 503f47940a1b56a46e0d10ca107058d676efe3f4 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 15 Jul 2021 15:02:15 +1200 Subject: [PATCH 02/12] Fix typo --- src/Utilities/model.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 6c2219d443..e585683cb8 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -28,7 +28,7 @@ function _add_variables(::Nothing, ::Int64) end function MOI.add_variable(model::AbstractModel{T}) where {T} vi = VI(model.num_variables_created += 1) push!(model.single_variable_mask, 0x0) - __add_free(model.variable_bounds) + _add_free(model.variable_bounds) if model.variable_indices !== nothing push!(model.variable_indices, vi) end From e4055878ba2b2677debb6483c93d61efb904793c Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 15 Jul 2021 16:38:20 +1200 Subject: [PATCH 03/12] Unify Box interface --- src/Utilities/box.jl | 231 ++++++++++++++++++++++++++++------------- src/Utilities/model.jl | 87 ++++------------ test/Utilities/box.jl | 192 +++++++++++++++++++++++++++------- 3 files changed, 333 insertions(+), 177 deletions(-) diff --git a/src/Utilities/box.jl b/src/Utilities/box.jl index 802e22d09f..b84b3c48ba 100644 --- a/src/Utilities/box.jl +++ b/src/Utilities/box.jl @@ -1,3 +1,45 @@ +""" + struct Box{T} + lower::Vector{T} + upper::Vector{T} + end + +Stores the constants of scalar constraints with the lower bound of the set in +`lower` and the upper bound in `upper`. +""" +struct Box{T} + set_mask::Vector{UInt8} + lower::Vector{T} + upper::Vector{T} +end + +Box{T}() where {T} = Box{T}(UInt8[], T[], T[]) + +function Base.:(==)(a::Box, b::Box) + return a.set_mask == b.set_mask && a.lower == b.lower && a.upper == b.upper +end + +function Base.empty!(b::Box) + empty!(b.set_mask) + empty!(b.lower) + empty!(b.upper) + return b +end + +function Base.resize!(b::Box, n) + resize!(b.set_mask, n) + resize!(b.lower, n) + resize!(b.upper, n) + return +end + +function _add_variable(b::Box{T}) where {T} + push!(b.set_mask, 0x00) + push!(b.lower, _no_lower_bound(T)) + push!(b.upper, _no_upper_bound(T)) + return +end + """ SUPPORTED_VARIABLE_SCALAR_SETS{T} @@ -16,22 +58,20 @@ const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{ } # 0xcb = 0x80 | 0x40 | 0x8 | 0x2 | 0x1 -const LOWER_BOUND_MASK = 0xcb +const _LOWER_BOUND_MASK = 0xcb # 0xcd = 0x80 | 0x40 | 0x8 | 0x4 | 0x1 -const UPPER_BOUND_MASK = 0xcd - -single_variable_flag(::Type{<:MOI.EqualTo}) = 0x1 -single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x2 -single_variable_flag(::Type{<:MOI.LessThan}) = 0x4 -single_variable_flag(::Type{<:MOI.Interval}) = 0x8 -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, ::Type{T}) where {T} +const _UPPER_BOUND_MASK = 0xcd + +_single_variable_flag(::Type{<:MOI.EqualTo}) = 0x1 +_single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x2 +_single_variable_flag(::Type{<:MOI.LessThan}) = 0x4 +_single_variable_flag(::Type{<:MOI.Interval}) = 0x8 +_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 + +function _flag_to_set_type(flag::UInt8, ::Type{T}) where {T} if flag == 0x1 return MOI.EqualTo{T} elseif flag == 0x2 @@ -54,38 +94,38 @@ end # Julia doesn't infer `S1` correctly, so we use a function barrier to improve # inference. -function _throw_if_lower_bound_set(variable, S2, mask, T) - S1 = flag_to_set_type(mask, T) +function _throw_if_lower_bound_set_inner(variable, S2, mask, T) + S1 = _flag_to_set_type(mask, T) throw(MOI.LowerBoundAlreadySet{S1,S2}(variable)) return end -function throw_if_lower_bound_set(variable, S2, mask, T) - lower_mask = mask & LOWER_BOUND_MASK +function _throw_if_lower_bound_set(variable, S2, mask, T) + lower_mask = mask & _LOWER_BOUND_MASK if iszero(lower_mask) return # No lower bound set. - elseif iszero(single_variable_flag(S2) & LOWER_BOUND_MASK) + elseif iszero(_single_variable_flag(S2) & _LOWER_BOUND_MASK) return # S2 isn't related to the lower bound. end - return _throw_if_lower_bound_set(variable, S2, lower_mask, T) + return _throw_if_lower_bound_set_inner(variable, S2, lower_mask, T) end # Julia doesn't infer `S1` correctly, so we use a function barrier to improve # inference. -function _throw_if_upper_bound_set(variable, S2, mask, T) - S1 = flag_to_set_type(mask, T) +function _throw_if_upper_bound_set_inner(variable, S2, mask, T) + S1 = _flag_to_set_type(mask, T) throw(MOI.UpperBoundAlreadySet{S1,S2}(variable)) return end -function throw_if_upper_bound_set(variable, S2, mask, T) - upper_mask = mask & UPPER_BOUND_MASK +function _throw_if_upper_bound_set(variable, S2, mask, T) + upper_mask = mask & _UPPER_BOUND_MASK if iszero(upper_mask) return # No upper bound set. - elseif iszero(single_variable_flag(S2) & UPPER_BOUND_MASK) + elseif iszero(_single_variable_flag(S2) & _UPPER_BOUND_MASK) return # S2 isn't related to the upper bound. end - return _throw_if_upper_bound_set(variable, S2, upper_mask, T) + return _throw_if_upper_bound_set_inner(variable, S2, upper_mask, T) end function _lower_bound( @@ -104,58 +144,120 @@ end _upper_bound(set::MOI.EqualTo) = set.value -""" - struct Box{T} - lower::Vector{T} - upper::Vector{T} - end +# Use `-Inf` and `Inf` for `AbstractFloat` subtypes. +_no_lower_bound(::Type{T}) where {T} = zero(T) +_no_lower_bound(::Type{T}) where {T<:AbstractFloat} = typemin(T) +_no_upper_bound(::Type{T}) where {T} = zero(T) +_no_upper_bound(::Type{T}) where {T<:AbstractFloat} = typemax(T) -Stores the constants of scalar constraints with the lower bound of the set in -`lower` and the upper bound in `upper`. -""" -struct Box{T} - lower::Vector{T} - upper::Vector{T} +function MOI.add_constraint( + b::Box{T}, + f::MOI.SingleVariable, + set::S, +) where {T,S} + flag = _single_variable_flag(S) + mask = b.set_mask[f.variable.value] + _throw_if_lower_bound_set(f.variable, S, mask, T) + _throw_if_upper_bound_set(f.variable, S, mask, T) + if !iszero(flag & _LOWER_BOUND_MASK) + b.lower[f.variable.value] = _lower_bound(set) + end + if !iszero(flag & _UPPER_BOUND_MASK) + b.upper[f.variable.value] = _upper_bound(set) + end + b.set_mask[f.variable.value] = mask | flag + return MOI.ConstraintIndex{MOI.SingleVariable,S}(f.variable.value) end -Box{T}() where {T} = Box{T}(T[], T[]) +function MOI.delete( + b::Box{T}, + ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, +) where {T,S} + flag = _single_variable_flag(S) + b.set_mask[ci.value] &= ~flag + if !iszero(flag & _LOWER_BOUND_MASK) + b.lower[ci.value] = _no_lower_bound(T) + end + if !iszero(flag & _UPPER_BOUND_MASK) + b.upper[ci.value] = _no_upper_bound(T) + end + return +end -Base.:(==)(a::Box, b::Box) = a.lower == b.lower && a.upper == b.upper +function MOI.is_valid( + b::Box, + ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, +) where {S} + if !(1 <= ci.value <= length(b.set_mask)) + return false + end + return !iszero(b.set_mask[ci.value] & _single_variable_flag(S)) +end -function Base.empty!(b::Box) - empty!(b.lower) - empty!(b.upper) - return b +function MOI.get( + b::Box, + ::MOI.NumberOfConstraints{MOI.SingleVariable,S}, +) where {S} + flag = _single_variable_flag(S) + return count(mask -> !iszero(flag & mask), b.set_mask) end -function Base.resize!(b::Box, n) - resize!(b.lower, n) - resize!(b.upper, n) +function _add_constraint_type(list, b::Box, S::Type{<:MOI.AbstractScalarSet}) + flag = _single_variable_flag(S)::UInt8 + if any(mask -> !iszero(flag & mask), b.set_mask) + push!(list, (MOI.SingleVariable, S)) + end return end -# Use `-Inf` and `Inf` for `AbstractFloat` subtypes. -_no_lower_bound(::Type{T}) where {T} = zero(T) -_no_lower_bound(::Type{T}) where {T<:AbstractFloat} = typemin(T) -_no_upper_bound(::Type{T}) where {T} = zero(T) -_no_upper_bound(::Type{T}) where {T<:AbstractFloat} = typemax(T) +function MOI.get(b::Box{T}, ::MOI.ListOfConstraintTypesPresent) where {T} + list = Tuple{DataType,DataType}[] + _add_constraint_type(list, b, MOI.EqualTo{T}) + _add_constraint_type(list, b, MOI.GreaterThan{T}) + _add_constraint_type(list, b, MOI.LessThan{T}) + _add_constraint_type(list, b, MOI.Interval{T}) + _add_constraint_type(list, b, MOI.Semicontinuous{T}) + _add_constraint_type(list, b, MOI.Semiinteger{T}) + _add_constraint_type(list, b, MOI.Integer) + _add_constraint_type(list, b, MOI.ZeroOne) + return list +end + +function MOI.get( + b::Box, + ::MOI.ListOfConstraintIndices{MOI.SingleVariable,S}, +) where {S} + list = MOI.ConstraintIndex{MOI.SingleVariable,S}[] + flag = _single_variable_flag(S) + for (index, mask) in enumerate(b.set_mask) + if !iszero(mask & flag) + push!(list, MOI.ConstraintIndex{MOI.SingleVariable,S}(index)) + end + end + return list +end + +### +### MatrixOfConstraints API +### function load_constants( b::Box{T}, offset, set::SUPPORTED_VARIABLE_SCALAR_SETS{T}, ) where {T} - flag = single_variable_flag(typeof(set)) - if iszero(flag & LOWER_BOUND_MASK) + flag = _single_variable_flag(typeof(set)) + if iszero(flag & _LOWER_BOUND_MASK) b.lower[offset+1] = _no_lower_bound(T) else b.lower[offset+1] = _lower_bound(set) end - if iszero(flag & UPPER_BOUND_MASK) + if iszero(flag & _UPPER_BOUND_MASK) b.upper[offset+1] = _no_upper_bound(T) else b.upper[offset+1] = _upper_bound(set) end + b.set_mask[offset+1] |= flag return end @@ -193,22 +295,3 @@ function set_from_constants( ) return S() end - -# Function used in MOI.Utilities.AbstractModel. - -function _merge_bounds(b::Box, index, set) - flag = single_variable_flag(typeof(set)) - if !iszero(flag & LOWER_BOUND_MASK) - b.lower[index] = _lower_bound(set) - end - if !iszero(flag & UPPER_BOUND_MASK) - b.upper[index] = _upper_bound(set) - end - return -end - -function _add_free(b::Box{T}) where {T} - push!(b.lower, _no_lower_bound(T)) - push!(b.upper, _no_upper_bound(T)) - return -end diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index e585683cb8..64b0238389 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -25,13 +25,14 @@ function _add_variable end function _add_variable(::Nothing) end function _add_variables(::Nothing, ::Int64) end -function MOI.add_variable(model::AbstractModel{T}) where {T} - vi = VI(model.num_variables_created += 1) - push!(model.single_variable_mask, 0x0) - _add_free(model.variable_bounds) + +function MOI.add_variable(model::AbstractModel) + model.num_variables_created += 1 + vi = MOI.VariableIndex(model.num_variables_created) if model.variable_indices !== nothing push!(model.variable_indices, vi) end + _add_variable(model.variable_bounds) _add_variable(model.constraints) return vi end @@ -81,7 +82,8 @@ function _delete_variable( vi::MOI.VariableIndex, ) where {T} MOI.throw_if_not_valid(model, vi) - model.single_variable_mask[vi.value] = 0x0 + # TODO(odow): is there a better way? + model.variable_bounds[vi.value].set_mask = 0x00 if model.variable_indices === nothing model.variable_indices = Set(MOI.get(model, MOI.ListOfVariableIndices())) @@ -160,11 +162,9 @@ function MOI.is_valid( model::AbstractModel, ci::CI{MOI.SingleVariable,S}, ) where {S} - return 1 ≤ ci.value ≤ length(model.single_variable_mask) && - !iszero( - model.single_variable_mask[ci.value] & single_variable_flag(S), - ) + return MOI.is_valid(model.variable_bounds, ci) end + function MOI.is_valid(model::AbstractModel, ci::MOI.ConstraintIndex) return MOI.is_valid(model.constraints, ci) end @@ -431,15 +431,7 @@ function MOI.add_constraint( f::MOI.SingleVariable, s::SUPPORTED_VARIABLE_SCALAR_SETS{T}, ) where {T} - flag = single_variable_flag(typeof(s)) - index = f.variable.value - mask = model.single_variable_mask[index] - throw_if_lower_bound_set(f.variable, typeof(s), mask, T) - throw_if_upper_bound_set(f.variable, typeof(s), mask, T) - # No error should be thrown now, we can modify `model`. - _merge_bounds(model.variable_bounds, index, s) - model.single_variable_mask[index] = mask | flag - return CI{MOI.SingleVariable,typeof(s)}(index) + return MOI.add_constraint(model.variable_bounds, f, s) end function MOI.add_constraint( @@ -459,18 +451,11 @@ function MOI.get( end function _delete_constraint( - model::AbstractModel{T}, + model::AbstractModel, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, -) where {T,S} +) where {S} MOI.throw_if_not_valid(model, ci) - flag = single_variable_flag(S) - model.single_variable_mask[ci.value] &= ~flag - if !iszero(flag & LOWER_BOUND_MASK) - model.variable_bounds.lower[ci.value] = _no_lower_bound(T) - end - if !iszero(flag & UPPER_BOUND_MASK) - model.variable_bounds.upper[ci.value] = _no_upper_bound(T) - end + MOI.delete(model.variable_bounds, ci) return end @@ -535,11 +520,11 @@ end function MOI.get( model::AbstractModel, - ::MOI.NumberOfConstraints{MOI.SingleVariable,S}, + attr::MOI.NumberOfConstraints{MOI.SingleVariable,S}, ) where {S} - flag = single_variable_flag(S) - return count(mask -> !iszero(flag & mask), model.single_variable_mask) + return MOI.get(model.variable_bounds, attr) end + function MOI.get( model::AbstractModel, noc::MOI.NumberOfConstraints{F,S}, @@ -547,45 +532,21 @@ function MOI.get( return MOI.get(model.constraints, noc) end -function _add_constraint_type( - list, - model::AbstractModel, - S::Type{<:MOI.AbstractScalarSet}, -) - flag = single_variable_flag(S)::UInt8 - if any(mask -> !iszero(flag & mask), model.single_variable_mask) - push!(list, (MOI.SingleVariable, S)) - end - return -end function MOI.get( model::AbstractModel{T}, attr::MOI.ListOfConstraintTypesPresent, ) where {T} - list = MOI.get(model.constraints, attr)::Vector{Tuple{DataType,DataType}} - _add_constraint_type(list, model, MOI.EqualTo{T}) - _add_constraint_type(list, model, MOI.GreaterThan{T}) - _add_constraint_type(list, model, MOI.LessThan{T}) - _add_constraint_type(list, model, MOI.Interval{T}) - _add_constraint_type(list, model, MOI.Semicontinuous{T}) - _add_constraint_type(list, model, MOI.Semiinteger{T}) - _add_constraint_type(list, model, MOI.Integer) - _add_constraint_type(list, model, MOI.ZeroOne) - return list + return vcat( + MOI.get(model.constraints, attr)::Vector{Tuple{DataType,DataType}}, + MOI.get(model.variable_bounds, attr)::Vector{Tuple{DataType,DataType}}, + ) end function MOI.get( model::AbstractModel, - ::MOI.ListOfConstraintIndices{MOI.SingleVariable,S}, + attr::MOI.ListOfConstraintIndices{MOI.SingleVariable,S}, ) where {S} - list = CI{MOI.SingleVariable,S}[] - flag = single_variable_flag(S) - for (index, mask) in enumerate(model.single_variable_mask) - if !iszero(mask & flag) - push!(list, CI{MOI.SingleVariable,S}(index)) - end - end - return list + return MOI.get(model.variable_bounds, attr) end function MOI.get( @@ -637,7 +598,6 @@ function MOI.empty!(model::AbstractModel{T}) where {T} model.objective = zero(MOI.ScalarAffineFunction{T}) model.num_variables_created = 0 model.variable_indices = nothing - model.single_variable_mask = UInt8[] empty!(model.variable_bounds) empty!(model.var_to_name) model.name_to_var = nothing @@ -927,9 +887,6 @@ for (loop_name, loop_super_type) in [ # If nothing, no variable has been deleted so the indices of the # variables are VI.(1:num_variables_created) variable_indices::Union{Nothing,Set{VI}} - # Union of flags of `S` such that a `SingleVariable`-in-`S` - # constraint was added to the model and not deleted yet. - single_variable_mask::Vector{UInt8} # Bounds set by `SingleVariable`-in-`S`: variable_bounds::Box{T} constraints::C diff --git a/test/Utilities/box.jl b/test/Utilities/box.jl index 0475b2e965..61378165a1 100644 --- a/test/Utilities/box.jl +++ b/test/Utilities/box.jl @@ -15,58 +15,195 @@ function runtests() return end -function test_flag_to_set_type() - T = Int - @test_throws AssertionError MOI.Utilities.flag_to_set_type(0x11, T) - @test MOI.Utilities.flag_to_set_type(0x10, T) == MOI.Integer - @test MOI.Utilities.flag_to_set_type(0x20, T) == MOI.ZeroOne - return -end - function test_equal() - a = MOI.Utilities.Box([1, 2], [3, 4]) - b = MOI.Utilities.Box([1.0, 2.0], [3.0, 4.0]) - c = MOI.Utilities.Box([1.0, 2.0], [3.0, 5.0]) + a = MOI.Utilities.Box([0x00, 0x00], [1, 2], [3, 4]) + b = MOI.Utilities.Box([0x00, 0x00], [1.0, 2.0], [3.0, 4.0]) + c = MOI.Utilities.Box([0x00, 0x02], [1.0, 2.0], [3.0, 4.0]) + d = MOI.Utilities.Box([0x00, 0x00], [1.0, 3.0], [3.0, 4.0]) + e = MOI.Utilities.Box([0x00, 0x00], [1.0, 2.0], [3.0, 5.0]) @test a == a @test a == b @test a != c + @test a != d + @test a != e return end function test_empty() - a = MOI.Utilities.Box([1, 2], [3, 4]) + a = MOI.Utilities.Box([0x00, 0x00], [1, 2], [3, 4]) empty!(a) @test a == MOI.Utilities.Box{Int}() return end function test_resize() - a = MOI.Utilities.Box([1, 2], [3, 4]) + a = MOI.Utilities.Box([0x00, 0x00], [1, 2], [3, 4]) + @test length(a.set_mask) == 2 @test length(a.lower) == 2 @test length(a.upper) == 2 resize!(a, 4) + @test length(a.set_mask) == 4 @test length(a.lower) == 4 @test length(a.upper) == 4 return end +function test_add_variable() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], [0], [0]) + a = MOI.Utilities.Box{Float64}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Float64}([0x00], [-Inf], [Inf]) + return +end + +function test__flag_to_set_type() + T = Int + @test_throws AssertionError MOI.Utilities._flag_to_set_type(0x11, T) + @test MOI.Utilities._flag_to_set_type(0x10, T) == MOI.Integer + @test MOI.Utilities._flag_to_set_type(0x20, T) == MOI.ZeroOne + return +end + +function test_add_constraint() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + MOI.add_constraint(a, f, MOI.GreaterThan(3)) + @test a == MOI.Utilities.Box{Int}([0x02], [3], [0]) + MOI.add_constraint(a, f, MOI.LessThan(4)) + @test a == MOI.Utilities.Box{Int}([0x06], [3], [4]) + return +end + +function test_add_constraint_LowerBoundAlreadySet() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + MOI.add_constraint(a, f, MOI.GreaterThan(3)) + @test_throws( + MOI.LowerBoundAlreadySet{MOI.GreaterThan{Int},MOI.GreaterThan{Int}}, + MOI.add_constraint(a, f, MOI.GreaterThan(3)), + ) + return +end + +function test_add_constraint_UpperBoundAlreadySet() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + MOI.add_constraint(a, f, MOI.LessThan(3)) + @test_throws( + MOI.UpperBoundAlreadySet{MOI.LessThan{Int},MOI.LessThan{Int}}, + MOI.add_constraint(a, f, MOI.LessThan(3)), + ) + return +end + +function test_delete_constraint_LessThan() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + c = MOI.add_constraint(a, f, MOI.LessThan(3)) + @test MOI.is_valid(a, c) + MOI.delete(a, c) + @test !MOI.is_valid(a, c) + return +end + +function test_delete_constraint_GreaterThan() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + c = MOI.add_constraint(a, f, MOI.GreaterThan(3)) + @test MOI.is_valid(a, c) + MOI.delete(a, c) + @test !MOI.is_valid(a, c) + return +end + +function test_NumberOfConstraints() + b = MOI.Utilities.Box( + [0x08, 0x02, 0x04, 0x06], + [1.0, 3.0, -Inf, -1.0], + [2.0, Inf, 4.0, 1.0], + ) + get(S) = MOI.get(b, MOI.NumberOfConstraints{MOI.SingleVariable,S}()) + @test get(MOI.ZeroOne) == 0 + @test get(MOI.GreaterThan{Float64}) == 2 + @test get(MOI.LessThan{Float64}) == 2 + @test get(MOI.Interval{Float64}) == 1 + return +end + +function test_ListOfConstraintTypesPresent() + b = MOI.Utilities.Box( + [0x08, 0x02, 0x04, 0x06], + [1.0, 3.0, -Inf, -1.0], + [2.0, Inf, 4.0, 1.0], + ) + @test MOI.get(b, MOI.ListOfConstraintTypesPresent()) == [ + (MOI.SingleVariable, MOI.GreaterThan{Float64}), + (MOI.SingleVariable, MOI.LessThan{Float64}), + (MOI.SingleVariable, MOI.Interval{Float64}), + ] + return +end + +function test_ListOfConstraintIndices() + b = MOI.Utilities.Box( + [0x08, 0x02, 0x04, 0x06], + [1.0, 3.0, -Inf, -1.0], + [2.0, Inf, 4.0, 1.0], + ) + get(S) = MOI.get(b, MOI.ListOfConstraintIndices{MOI.SingleVariable,S}()) + @test get(MOI.ZeroOne) == [] + @test get(MOI.GreaterThan{Float64}) == [ + MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}(2), + MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}(4), + ] + @test get(MOI.LessThan{Float64}) == + MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}.([3, 4]) + @test get(MOI.Interval{Float64}) == + [MOI.ConstraintIndex{MOI.SingleVariable,MOI.Interval{Float64}}(1)] + return +end + function test_load_constants() - a = MOI.Utilities.Box([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) + a = MOI.Utilities.Box( + [0x00, 0x00, 0x00], + [-Inf, -Inf, -Inf], + [Inf, Inf, Inf], + ) MOI.Utilities.load_constants(a, 0, MOI.Interval(1.0, 2.0)) MOI.Utilities.load_constants(a, 1, MOI.GreaterThan(3.0)) MOI.Utilities.load_constants(a, 2, MOI.LessThan(4.0)) - @test a == MOI.Utilities.Box([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + @test a == MOI.Utilities.Box( + [0x08, 0x02, 0x04], + [1.0, 3.0, -Inf], + [2.0, Inf, 4.0], + ) return end function test_function_constants() - a = MOI.Utilities.Box([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) + a = MOI.Utilities.Box( + [0x00, 0x00, 0x00], + [-Inf, -Inf, -Inf], + [Inf, Inf, Inf], + ) MOI.Utilities.function_constants(a, 0) == 0.0 return end function test_set_from_constants() - a = MOI.Utilities.Box([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + a = MOI.Utilities.Box([0x00, 0x00, 0x00], [1.0, 3.0, -Inf], [2.0, Inf, 4.0]) @test MOI.Utilities.set_from_constants(a, MOI.Interval{Float64}, 1) == MOI.Interval(1.0, 2.0) @test MOI.Utilities.set_from_constants(a, MOI.GreaterThan{Float64}, 2) == @@ -77,27 +214,6 @@ function test_set_from_constants() return end -function test_merge_bounds() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_free(a) - @test a == MOI.Utilities.Box{Int}(Int[0], Int[0]) - MOI.Utilities.load_constants(a, 0, MOI.GreaterThan(3)) - @test a == MOI.Utilities.Box{Int}(Int[3], Int[0]) - MOI.Utilities._merge_bounds(a, 1, MOI.LessThan(4)) - @test a == MOI.Utilities.Box{Int}(Int[3], Int[4]) - return -end - -function test_add_free() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_free(a) - @test a == MOI.Utilities.Box{Int}(Int[0], Int[0]) - a = MOI.Utilities.Box{Float64}() - MOI.Utilities._add_free(a) - @test a == MOI.Utilities.Box{Float64}(Float64[-Inf], Float64[Inf]) - return -end - end # module TestBox.runtests() From 0c99de041b52db6637afd2d935d687881665f5f6 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 15 Jul 2021 17:03:34 +1200 Subject: [PATCH 04/12] Fix --- src/Utilities/model.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 64b0238389..b928bbffd1 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -907,7 +907,6 @@ for (loop_name, loop_super_type) in [ zero(MOI.ScalarAffineFunction{T}), 0, nothing, - UInt8[], Box{T}(), C(), Dict{MOI.VariableIndex,String}(), From aff156b585f1861888b04cd1c426b391c1089ceb Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 16 Jul 2021 13:03:36 +1200 Subject: [PATCH 05/12] Fix delete --- src/Utilities/box.jl | 6 ++++++ src/Utilities/model.jl | 4 ++-- test/Utilities/box.jl | 11 +++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Utilities/box.jl b/src/Utilities/box.jl index b84b3c48ba..5a9d93399a 100644 --- a/src/Utilities/box.jl +++ b/src/Utilities/box.jl @@ -184,6 +184,12 @@ function MOI.delete( return end +function MOI.delete(b::Box, x::MOI.VariableIndex) + # To "delete" the variable, set it to 0x00 (free). + b.set_mask[x.value] = 0x00 + return +end + function MOI.is_valid( b::Box, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index b928bbffd1..b3088e67d8 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -77,13 +77,13 @@ function filter_variables( end return g, t end + function _delete_variable( model::AbstractModel{T}, vi::MOI.VariableIndex, ) where {T} MOI.throw_if_not_valid(model, vi) - # TODO(odow): is there a better way? - model.variable_bounds[vi.value].set_mask = 0x00 + MOI.delete(model.variable_bounds, vi) if model.variable_indices === nothing model.variable_indices = Set(MOI.get(model, MOI.ListOfVariableIndices())) diff --git a/test/Utilities/box.jl b/test/Utilities/box.jl index 61378165a1..fc4e9bf812 100644 --- a/test/Utilities/box.jl +++ b/test/Utilities/box.jl @@ -116,6 +116,17 @@ function test_delete_constraint_LessThan() return end +function test_delete_variable() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + c = MOI.add_constraint(a, f, MOI.LessThan(3)) + MOI.delete(a, MOI.VariableIndex(1)) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[3]) + return +end + function test_delete_constraint_GreaterThan() a = MOI.Utilities.Box{Int}() MOI.Utilities._add_variable(a) From 36bae1257c2faa4203f2f5f65fde53eaef93b390 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 16 Jul 2021 13:46:19 +1200 Subject: [PATCH 06/12] More fixes --- src/Utilities/box.jl | 16 ++++++++++++++++ src/Utilities/model.jl | 5 +++-- test/Utilities/box.jl | 12 ++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Utilities/box.jl b/src/Utilities/box.jl index 5a9d93399a..a56de71300 100644 --- a/src/Utilities/box.jl +++ b/src/Utilities/box.jl @@ -200,6 +200,22 @@ function MOI.is_valid( return !iszero(b.set_mask[ci.value] & _single_variable_flag(S)) end +function MOI.set( + b::Box, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, + set::S, +) where {S} + flag = _single_variable_flag(S) + if !iszero(flag & _LOWER_BOUND_MASK) + b.lower[ci.value] = _lower_bound(set) + end + if !iszero(flag & _UPPER_BOUND_MASK) + b.upper[ci.value] = _upper_bound(set) + end + return +end + function MOI.get( b::Box, ::MOI.NumberOfConstraints{MOI.SingleVariable,S}, diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index b3088e67d8..374b192103 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -487,14 +487,15 @@ function MOI.set( ) return throw(MOI.SettingSingleVariableFunctionNotAllowed()) end + function MOI.set( model::AbstractModel{T}, - ::MOI.ConstraintSet, + attr::MOI.ConstraintSet, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, set::S, ) where {T,S<:SUPPORTED_VARIABLE_SCALAR_SETS{T}} MOI.throw_if_not_valid(model, ci) - _merge_bounds(model.variable_bounds, ci.value, set) + MOI.set(model.variable_bounds, attr, ci, set) return end diff --git a/test/Utilities/box.jl b/test/Utilities/box.jl index fc4e9bf812..87d6b97712 100644 --- a/test/Utilities/box.jl +++ b/test/Utilities/box.jl @@ -139,6 +139,18 @@ function test_delete_constraint_GreaterThan() return end +function test_set_ConstraintSet() + a = MOI.Utilities.Box{Int}() + MOI.Utilities._add_variable(a) + @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) + f = MOI.SingleVariable(MOI.VariableIndex(1)) + c = MOI.add_constraint(a, f, MOI.GreaterThan(3)) + @test a == MOI.Utilities.Box{Int}([0x02], Int[3], Int[0]) + MOI.set(a, MOI.ConstraintSet(), c, MOI.GreaterThan(2)) + @test a == MOI.Utilities.Box{Int}([0x02], Int[2], Int[0]) + return +end + function test_NumberOfConstraints() b = MOI.Utilities.Box( [0x08, 0x02, 0x04, 0x06], From b5acfd6661bd89ed610f4e21337f3c0a3c5c1f7f Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 16 Jul 2021 14:59:30 +1200 Subject: [PATCH 07/12] Fix --- test/Utilities/matrix_of_constraints.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index 74f71a2fdf..8b2ef5395b 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -192,13 +192,17 @@ function test_contlinear() end function test_contlinear(Indexing) A2 = sparse([1, 1], [1, 2], ones(2)) - b2 = MOI.Utilities.Box([-Inf], [1.0]) + b2 = MOI.Utilities.Box([0x04], [-Inf], [1.0]) Alp = sparse( [1, 1, 2, 3, 4, 4], [1, 2, 1, 2, 1, 2], Float64[3, 2, 1, -1, 5, -4], ) - blp = MOI.Utilities.Box([5, 0, -Inf, 6], [5, Inf, 0, 7]) + blp = MOI.Utilities.Box( + [0x01, 0x02, 0x04, 0x08], + [5, 0, -Inf, 6], + [5, Inf, 0, 7], + ) F = MOI.ScalarAffineFunction{Float64} @testset "$SetType" for SetType in [MixLP{Float64}, OrdLP{Float64}] _test( From fc41b47a26103555a4259a3cc046f521242150f1 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 16 Jul 2021 17:16:42 +1200 Subject: [PATCH 08/12] More refactoring --- docs/src/submodules/Utilities/overview.md | 2 +- docs/src/submodules/Utilities/reference.md | 2 +- src/Utilities/Utilities.jl | 2 +- src/Utilities/model.jl | 58 +---- src/Utilities/{box.jl => vector_bounds.jl} | 248 ++++++++++++++------ test/Utilities/matrix_of_constraints.jl | 30 +-- test/Utilities/{box.jl => vector_bounds.jl} | 218 +++++++++++------ 7 files changed, 341 insertions(+), 219 deletions(-) rename src/Utilities/{box.jl => vector_bounds.jl} (67%) rename test/Utilities/{box.jl => vector_bounds.jl} (55%) diff --git a/docs/src/submodules/Utilities/overview.md b/docs/src/submodules/Utilities/overview.md index fbeb038084..ddb574f483 100644 --- a/docs/src/submodules/Utilities/overview.md +++ b/docs/src/submodules/Utilities/overview.md @@ -410,7 +410,7 @@ const Model = MOI.Utilities.GenericModel{ MOI.Utilities.MatrixOfConstraints{ Float64, MOI.Utilities.MutableSparseMatrixCSC{Float64,Cint,MOI.Utilities.ZeroBasedIndexing}, - MOI.Utilities.Box{Float64}, + MOI.Utilities.MatrixBounds{Float64}, LP{Float64}, }, } diff --git a/docs/src/submodules/Utilities/reference.md b/docs/src/submodules/Utilities/reference.md index dc5fc69737..17b9caf426 100644 --- a/docs/src/submodules/Utilities/reference.md +++ b/docs/src/submodules/Utilities/reference.md @@ -113,7 +113,7 @@ Utilities.set_from_constants ``` ```@docs -Utilities.Box +Utilities.MatrixBounds ``` ### `.sets` diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index 71cfcf0e5d..6782ae72e5 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -59,7 +59,7 @@ include("copy.jl") include("results.jl") include("variables.jl") -include("box.jl") +include("vector_bounds.jl") include("vector_of_constraints.jl") include("struct_of_constraints.jl") include("model.jl") diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 374b192103..ef015b285a 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -6,12 +6,8 @@ abstract type AbstractOptimizer{T} <: MOI.AbstractOptimizer end const AbstractModel{T} = Union{AbstractModelLike{T},AbstractOptimizer{T}} # Variables -function MOI.get(model::AbstractModel, ::MOI.NumberOfVariables)::Int64 - if model.variable_indices === nothing - return model.num_variables_created - else - return length(model.variable_indices) - end +function MOI.get(model::AbstractModel, attr::MOI.NumberOfVariables)::Int64 + return MOI.get(model.variable_bounds, attr) end """ @@ -27,14 +23,9 @@ function _add_variable(::Nothing) end function _add_variables(::Nothing, ::Int64) end function MOI.add_variable(model::AbstractModel) - model.num_variables_created += 1 - vi = MOI.VariableIndex(model.num_variables_created) - if model.variable_indices !== nothing - push!(model.variable_indices, vi) - end - _add_variable(model.variable_bounds) + x = MOI.add_variable(model.variable_bounds) _add_variable(model.constraints) - return vi + return x end function MOI.add_variables(model::AbstractModel, n::Integer) @@ -84,11 +75,6 @@ function _delete_variable( ) where {T} MOI.throw_if_not_valid(model, vi) MOI.delete(model.variable_bounds, vi) - if model.variable_indices === nothing - model.variable_indices = - Set(MOI.get(model, MOI.ListOfVariableIndices())) - end - delete!(model.variable_indices, vi) model.name_to_var = nothing delete!(model.var_to_name, vi) model.name_to_con = nothing @@ -141,8 +127,6 @@ end function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex}) if isempty(vis) - # In `keep`, we assume that `model.variable_indices !== nothing` so - # at least one variable need to be deleted. return end _throw_if_cannot_delete(model.constraints, vis, Set(vis)) @@ -152,7 +136,7 @@ function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex}) for vi in vis _delete_variable(model, vi) end - keep(vi::MOI.VariableIndex) = vi in model.variable_indices + keep = x -> MOI.is_valid(model, x) model.objective = filter_variables(keep, model.objective) model.name_to_con = nothing return @@ -169,22 +153,12 @@ function MOI.is_valid(model::AbstractModel, ci::MOI.ConstraintIndex) return MOI.is_valid(model.constraints, ci) end -function MOI.is_valid(model::AbstractModel, vi::VI) - if model.variable_indices === nothing - return 1 ≤ vi.value ≤ model.num_variables_created - else - return in(vi, model.variable_indices) - end +function MOI.is_valid(model::AbstractModel, x::MOI.VariableIndex) + return MOI.is_valid(model.variable_bounds, x) end -function MOI.get(model::AbstractModel, ::MOI.ListOfVariableIndices) - if model.variable_indices === nothing - return VI.(1:model.num_variables_created) - else - vis = collect(model.variable_indices) - sort!(vis, by = vi -> vi.value) # It needs to be sorted by order of creation - return vis - end +function MOI.get(model::AbstractModel, attr::MOI.ListOfVariableIndices) + return MOI.get(model.variable_bounds, attr) end # Names @@ -588,7 +562,6 @@ function MOI.is_empty(model::AbstractModel) !model.objectiveset && isempty(model.objective.terms) && iszero(model.objective.constant) && - iszero(model.num_variables_created) && MOI.is_empty(model.constraints) end function MOI.empty!(model::AbstractModel{T}) where {T} @@ -597,8 +570,6 @@ function MOI.empty!(model::AbstractModel{T}) where {T} model.sense = MOI.FEASIBILITY_SENSE model.objectiveset = false model.objective = zero(MOI.ScalarAffineFunction{T}) - model.num_variables_created = 0 - model.variable_indices = nothing empty!(model.variable_bounds) empty!(model.var_to_name) model.name_to_var = nothing @@ -884,12 +855,7 @@ for (loop_name, loop_super_type) in [ MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}, } - num_variables_created::Int64 - # If nothing, no variable has been deleted so the indices of the - # variables are VI.(1:num_variables_created) - variable_indices::Union{Nothing,Set{VI}} - # Bounds set by `SingleVariable`-in-`S`: - variable_bounds::Box{T} + variable_bounds::SingleVariableConstraints{T} constraints::C var_to_name::Dict{MOI.VariableIndex,String} # If `nothing`, the dictionary hasn't been constructed yet. @@ -906,9 +872,7 @@ for (loop_name, loop_super_type) in [ MOI.FEASIBILITY_SENSE, false, zero(MOI.ScalarAffineFunction{T}), - 0, - nothing, - Box{T}(), + SingleVariableConstraints{T}(), C(), Dict{MOI.VariableIndex,String}(), nothing, diff --git a/src/Utilities/box.jl b/src/Utilities/vector_bounds.jl similarity index 67% rename from src/Utilities/box.jl rename to src/Utilities/vector_bounds.jl index a56de71300..44f2fc3d27 100644 --- a/src/Utilities/box.jl +++ b/src/Utilities/vector_bounds.jl @@ -1,50 +1,51 @@ -""" - struct Box{T} - lower::Vector{T} - upper::Vector{T} - end +abstract type AbstractVectorBounds end -Stores the constants of scalar constraints with the lower bound of the set in -`lower` and the upper bound in `upper`. -""" -struct Box{T} - set_mask::Vector{UInt8} - lower::Vector{T} - upper::Vector{T} +function set_from_constants( + b::AbstractVectorBounds, + ::Type{<:MOI.EqualTo}, + index, +) + return MOI.EqualTo(b.lower[index]) end -Box{T}() where {T} = Box{T}(UInt8[], T[], T[]) - -function Base.:(==)(a::Box, b::Box) - return a.set_mask == b.set_mask && a.lower == b.lower && a.upper == b.upper +function set_from_constants( + b::AbstractVectorBounds, + S::Type{<:Union{MOI.GreaterThan,MOI.EqualTo}}, + index, +) + # Lower and upper bounds are equal for `EqualTo`, we can take either of them. + return S(b.lower[index]) end -function Base.empty!(b::Box) - empty!(b.set_mask) - empty!(b.lower) - empty!(b.upper) - return b +function set_from_constants( + b::AbstractVectorBounds, + S::Type{<:MOI.LessThan}, + index, +) + return S(b.upper[index]) end -function Base.resize!(b::Box, n) - resize!(b.set_mask, n) - resize!(b.lower, n) - resize!(b.upper, n) - return +function set_from_constants( + b::AbstractVectorBounds, + S::Type{<:Union{MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}}, + index, +) + return S(b.lower[index], b.upper[index]) end -function _add_variable(b::Box{T}) where {T} - push!(b.set_mask, 0x00) - push!(b.lower, _no_lower_bound(T)) - push!(b.upper, _no_upper_bound(T)) - return +function set_from_constants( + ::AbstractVectorBounds, + S::Type{<:Union{MOI.Integer,MOI.ZeroOne}}, + index, +) + return S() end """ SUPPORTED_VARIABLE_SCALAR_SETS{T} The union of scalar sets for `SingleVariable` constraints supported by -`Utilities.Box` (and therefore in `Utilities.Model`). +`Utilities.MatrixBounds` and `Utilities.SingleVariableConstraints`. """ const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{ MOI.EqualTo{T}, @@ -150,8 +151,91 @@ _no_lower_bound(::Type{T}) where {T<:AbstractFloat} = typemin(T) _no_upper_bound(::Type{T}) where {T} = zero(T) _no_upper_bound(::Type{T}) where {T<:AbstractFloat} = typemax(T) +### +### SingleVariableConstraints +### +### For use in MOI.Utilities.Model +### + +""" + struct SingleVariableConstraints{T} <: AbstractVectorBounds + variable_indices::Union{Nothing,Set{MOI.VariableIndex}} + set_mask::Vector{UInt8} + lower::Vector{T} + upper::Vector{T} + end + +A struct for storing SingleVariable-related constraints. Used in `MOI.Model`. +""" +mutable struct SingleVariableConstraints{T} <: AbstractVectorBounds + variable_indices::Union{Nothing,Set{MOI.VariableIndex}} + set_mask::Vector{UInt8} + lower::Vector{T} + upper::Vector{T} +end + +function SingleVariableConstraints{T}() where {T} + return SingleVariableConstraints{T}(nothing, UInt8[], T[], T[]) +end + +function Base.:(==)(a::SingleVariableConstraints, b::SingleVariableConstraints) + return a.variable_indices == a.variable_indices && + a.set_mask == b.set_mask && + a.lower == b.lower && + a.upper == b.upper +end + +function Base.empty!(b::SingleVariableConstraints) + b.variable_indices = nothing + empty!(b.set_mask) + empty!(b.lower) + empty!(b.upper) + return b +end + +function Base.resize!(b::SingleVariableConstraints, n) + resize!(b.set_mask, n) + resize!(b.lower, n) + resize!(b.upper, n) + return +end + +function MOI.add_variable(b::SingleVariableConstraints{T}) where {T} + push!(b.set_mask, 0x00) + push!(b.lower, _no_lower_bound(T)) + push!(b.upper, _no_upper_bound(T)) + x = MOI.VariableIndex(length(b.set_mask)) + if b.variable_indices !== nothing + push!(b.variable_indices, x) + end + return x +end + +function MOI.get(b::SingleVariableConstraints, ::MOI.ListOfVariableIndices) + if b.variable_indices === nothing + return MOI.VariableIndex.(1:length(b.set_mask)) + end + x = collect(b.variable_indices) + sort!(x, by = x -> x.value) + return x +end + +function MOI.is_valid(b::SingleVariableConstraints, x::MOI.VariableIndex) + if b.variable_indices === nothing + return 1 <= x.value <= length(b.set_mask) + end + return x in b.variable_indices +end + +function MOI.get(b::SingleVariableConstraints, ::MOI.NumberOfVariables)::Int64 + if b.variable_indices === nothing + return length(b.set_mask) + end + return length(b.variable_indices) +end + function MOI.add_constraint( - b::Box{T}, + b::SingleVariableConstraints{T}, f::MOI.SingleVariable, set::S, ) where {T,S} @@ -170,7 +254,7 @@ function MOI.add_constraint( end function MOI.delete( - b::Box{T}, + b::SingleVariableConstraints{T}, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, ) where {T,S} flag = _single_variable_flag(S) @@ -184,14 +268,18 @@ function MOI.delete( return end -function MOI.delete(b::Box, x::MOI.VariableIndex) +function MOI.delete(b::SingleVariableConstraints, x::MOI.VariableIndex) # To "delete" the variable, set it to 0x00 (free). b.set_mask[x.value] = 0x00 + if b.variable_indices === nothing + b.variable_indices = Set(MOI.get(b, MOI.ListOfVariableIndices())) + end + delete!(b.variable_indices, x) return end function MOI.is_valid( - b::Box, + b::SingleVariableConstraints, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, ) where {S} if !(1 <= ci.value <= length(b.set_mask)) @@ -201,7 +289,7 @@ function MOI.is_valid( end function MOI.set( - b::Box, + b::SingleVariableConstraints, ::MOI.ConstraintSet, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, set::S, @@ -217,14 +305,18 @@ function MOI.set( end function MOI.get( - b::Box, + b::SingleVariableConstraints, ::MOI.NumberOfConstraints{MOI.SingleVariable,S}, ) where {S} flag = _single_variable_flag(S) return count(mask -> !iszero(flag & mask), b.set_mask) end -function _add_constraint_type(list, b::Box, S::Type{<:MOI.AbstractScalarSet}) +function _add_constraint_type( + list, + b::SingleVariableConstraints, + S::Type{<:MOI.AbstractScalarSet}, +) flag = _single_variable_flag(S)::UInt8 if any(mask -> !iszero(flag & mask), b.set_mask) push!(list, (MOI.SingleVariable, S)) @@ -232,7 +324,10 @@ function _add_constraint_type(list, b::Box, S::Type{<:MOI.AbstractScalarSet}) return end -function MOI.get(b::Box{T}, ::MOI.ListOfConstraintTypesPresent) where {T} +function MOI.get( + b::SingleVariableConstraints{T}, + ::MOI.ListOfConstraintTypesPresent, +) where {T} list = Tuple{DataType,DataType}[] _add_constraint_type(list, b, MOI.EqualTo{T}) _add_constraint_type(list, b, MOI.GreaterThan{T}) @@ -246,7 +341,7 @@ function MOI.get(b::Box{T}, ::MOI.ListOfConstraintTypesPresent) where {T} end function MOI.get( - b::Box, + b::SingleVariableConstraints, ::MOI.ListOfConstraintIndices{MOI.SingleVariable,S}, ) where {S} list = MOI.ConstraintIndex{MOI.SingleVariable,S}[] @@ -260,11 +355,42 @@ function MOI.get( end ### -### MatrixOfConstraints API +### MatrixBounds ### +""" + struct MatrixBounds{T} <: AbstractVectorBounds + lower::Vector{T} + upper::Vector{T} + end + +A struct for the .constants field in MatrixOfConstraints. +""" +struct MatrixBounds{T} <: AbstractVectorBounds + lower::Vector{T} + upper::Vector{T} +end + +MatrixBounds{T}() where {T} = MatrixBounds{T}(T[], T[]) + +function Base.:(==)(a::MatrixBounds, b::MatrixBounds) + return a.lower == b.lower && a.upper == b.upper +end + +function Base.empty!(b::MatrixBounds) + empty!(b.lower) + empty!(b.upper) + return b +end + +function Base.resize!(b::MatrixBounds, n) + resize!(b.lower, n) + resize!(b.upper, n) + return +end + function load_constants( - b::Box{T}, + b::MatrixBounds{T}, offset, set::SUPPORTED_VARIABLE_SCALAR_SETS{T}, ) where {T} @@ -279,41 +405,7 @@ function load_constants( else b.upper[offset+1] = _upper_bound(set) end - b.set_mask[offset+1] |= flag return end -function_constants(::Box{T}, row) where {T} = zero(T) - -function set_from_constants(b::Box, ::Type{<:MOI.EqualTo}, index) - return MOI.EqualTo(b.lower[index]) -end - -function set_from_constants( - b::Box, - S::Type{<:Union{MOI.GreaterThan,MOI.EqualTo}}, - index, -) - # Lower and upper bounds are equal for `EqualTo`, we can take either of them. - return S(b.lower[index]) -end - -function set_from_constants(b::Box, S::Type{<:MOI.LessThan}, index) - return S(b.upper[index]) -end - -function set_from_constants( - b::Box, - S::Type{<:Union{MOI.Interval,MOI.Semicontinuous,MOI.Semiinteger}}, - index, -) - return S(b.lower[index], b.upper[index]) -end - -function set_from_constants( - ::Box, - S::Type{<:Union{MOI.Integer,MOI.ZeroOne}}, - index, -) - return S() -end +function_constants(::MatrixBounds{T}, row) where {T} = zero(T) diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index 8b2ef5395b..13d81c86c5 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -192,22 +192,18 @@ function test_contlinear() end function test_contlinear(Indexing) A2 = sparse([1, 1], [1, 2], ones(2)) - b2 = MOI.Utilities.Box([0x04], [-Inf], [1.0]) + b2 = MOI.Utilities.MatrixBounds([-Inf], [1.0]) Alp = sparse( [1, 1, 2, 3, 4, 4], [1, 2, 1, 2, 1, 2], Float64[3, 2, 1, -1, 5, -4], ) - blp = MOI.Utilities.Box( - [0x01, 0x02, 0x04, 0x08], - [5, 0, -Inf, 6], - [5, Inf, 0, 7], - ) + blp = MOI.Utilities.MatrixBounds([5, 0, -Inf, 6], [5, Inf, 0, 7]) F = MOI.ScalarAffineFunction{Float64} @testset "$SetType" for SetType in [MixLP{Float64}, OrdLP{Float64}] _test( MOIT.linear2test, - MOI.Utilities.Box{Float64}, + MOI.Utilities.MatrixBounds{Float64}, SetType, A2, b2, @@ -227,7 +223,7 @@ function test_contlinear(Indexing) end _test( _lp, - MOI.Utilities.Box{Float64}, + MOI.Utilities.MatrixBounds{Float64}, SetType, Alp, blp, @@ -367,7 +363,7 @@ end function test_get_by_name(T::Type, SetsType::Type) model = matrix_instance( T, - MOI.Utilities.Box{T}, + MOI.Utilities.MatrixBounds{T}, SetsType, MOI.Utilities.OneBasedIndexing, ) @@ -397,7 +393,7 @@ MOIU.@struct_of_constraints_by_function_types( function test_nametest() T = Float64 Indexing = MOIU.OneBasedIndexing - ConstantsType = MOIU.Box{T} + ConstantsType = MOIU.MatrixBounds{T} for ProductOfSetsType in [MixLP{Float64}, OrdLP{Float64}] model = MOIU.GenericOptimizer{ T, @@ -447,7 +443,7 @@ end function test_valid() T = Float64 Indexing = MOIU.OneBasedIndexing - ConstantsType = MOIU.Box{T} + ConstantsType = MOIU.MatrixBounds{T} for ProductOfSetsType in [MixLP{Float64}, OrdLP{Float64}] model = matrix_instance(T, ConstantsType, ProductOfSetsType, Indexing) MOI.DeprecatedTest.validtest(model, delete = false) @@ -456,7 +452,7 @@ end function test_supports_constraint(T::Type = Float64, BadT::Type = Float32) Indexing = MOIU.OneBasedIndexing - ConstantsType = MOIU.Box{T} + ConstantsType = MOIU.MatrixBounds{T} for ProductOfSetsType in [MixLP{Float64}, OrdLP{Float64}] model = MOIU.GenericOptimizer{ T, @@ -494,7 +490,7 @@ function test_copy(Indexing) MOIU.MatrixOfConstraints{ T, MOIU.MutableSparseMatrixCSC{T,Int,Indexing}, - MOIU.Box{T}, + MOIU.MatrixBounds{T}, ScalarSetsType, }, MOIU.MatrixOfConstraints{ @@ -515,8 +511,12 @@ function test_copy() end function test_modif() - model = - matrix_instance(Int, MOIU.Box{Int}, OrdLP{Int}, MOIU.OneBasedIndexing) + model = matrix_instance( + Int, + MOIU.MatrixBounds{Int}, + OrdLP{Int}, + MOIU.OneBasedIndexing, + ) x = MOI.add_variable(model) fx = MOI.SingleVariable(x) func = 2fx diff --git a/test/Utilities/box.jl b/test/Utilities/vector_bounds.jl similarity index 55% rename from test/Utilities/box.jl rename to test/Utilities/vector_bounds.jl index 87d6b97712..e7e412b0ad 100644 --- a/test/Utilities/box.jl +++ b/test/Utilities/vector_bounds.jl @@ -15,29 +15,25 @@ function runtests() return end -function test_equal() - a = MOI.Utilities.Box([0x00, 0x00], [1, 2], [3, 4]) - b = MOI.Utilities.Box([0x00, 0x00], [1.0, 2.0], [3.0, 4.0]) - c = MOI.Utilities.Box([0x00, 0x02], [1.0, 2.0], [3.0, 4.0]) - d = MOI.Utilities.Box([0x00, 0x00], [1.0, 3.0], [3.0, 4.0]) - e = MOI.Utilities.Box([0x00, 0x00], [1.0, 2.0], [3.0, 5.0]) - @test a == a - @test a == b - @test a != c - @test a != d - @test a != e - return -end - function test_empty() - a = MOI.Utilities.Box([0x00, 0x00], [1, 2], [3, 4]) + a = MOI.Utilities.SingleVariableConstraints( + Set([MOI.VariableIndex(1)]), + [0x00, 0x00], + [1, 2], + [3, 4], + ) empty!(a) - @test a == MOI.Utilities.Box{Int}() + @test a == MOI.Utilities.SingleVariableConstraints{Int}() return end function test_resize() - a = MOI.Utilities.Box([0x00, 0x00], [1, 2], [3, 4]) + a = MOI.Utilities.SingleVariableConstraints( + nothing, + [0x00, 0x00], + [1, 2], + [3, 4], + ) @test length(a.set_mask) == 2 @test length(a.lower) == 2 @test length(a.upper) == 2 @@ -49,12 +45,22 @@ function test_resize() end function test_add_variable() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], [0], [0]) - a = MOI.Utilities.Box{Float64}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Float64}([0x00], [-Inf], [Inf]) + a = MOI.Utilities.SingleVariableConstraints{Int}() + MOI.add_variable(a) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x00], + [0], + [0], + ) + a = MOI.Utilities.SingleVariableConstraints{Float64}() + MOI.add_variable(a) + @test a == MOI.Utilities.SingleVariableConstraints{Float64}( + nothing, + [0x00], + [-Inf], + [Inf], + ) return end @@ -67,22 +73,36 @@ function test__flag_to_set_type() end function test_add_constraint() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x00], + Int[0], + Int[0], + ) + f = MOI.SingleVariable(x) MOI.add_constraint(a, f, MOI.GreaterThan(3)) - @test a == MOI.Utilities.Box{Int}([0x02], [3], [0]) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x02], + [3], + [0], + ) MOI.add_constraint(a, f, MOI.LessThan(4)) - @test a == MOI.Utilities.Box{Int}([0x06], [3], [4]) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x06], + [3], + [4], + ) return end function test_add_constraint_LowerBoundAlreadySet() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + f = MOI.SingleVariable(x) MOI.add_constraint(a, f, MOI.GreaterThan(3)) @test_throws( MOI.LowerBoundAlreadySet{MOI.GreaterThan{Int},MOI.GreaterThan{Int}}, @@ -92,10 +112,9 @@ function test_add_constraint_LowerBoundAlreadySet() end function test_add_constraint_UpperBoundAlreadySet() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + f = MOI.SingleVariable(x) MOI.add_constraint(a, f, MOI.LessThan(3)) @test_throws( MOI.UpperBoundAlreadySet{MOI.LessThan{Int},MOI.LessThan{Int}}, @@ -105,10 +124,9 @@ function test_add_constraint_UpperBoundAlreadySet() end function test_delete_constraint_LessThan() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + f = MOI.SingleVariable(x) c = MOI.add_constraint(a, f, MOI.LessThan(3)) @test MOI.is_valid(a, c) MOI.delete(a, c) @@ -117,21 +135,30 @@ function test_delete_constraint_LessThan() end function test_delete_variable() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x00], + Int[0], + Int[0], + ) + f = MOI.SingleVariable(x) c = MOI.add_constraint(a, f, MOI.LessThan(3)) - MOI.delete(a, MOI.VariableIndex(1)) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[3]) + MOI.delete(a, x) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + Set{MOI.VariableIndex}(), + [0x00], + Int[0], + Int[3], + ) return end function test_delete_constraint_GreaterThan() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + f = MOI.SingleVariable(x) c = MOI.add_constraint(a, f, MOI.GreaterThan(3)) @test MOI.is_valid(a, c) MOI.delete(a, c) @@ -140,19 +167,35 @@ function test_delete_constraint_GreaterThan() end function test_set_ConstraintSet() - a = MOI.Utilities.Box{Int}() - MOI.Utilities._add_variable(a) - @test a == MOI.Utilities.Box{Int}([0x00], Int[0], Int[0]) - f = MOI.SingleVariable(MOI.VariableIndex(1)) + a = MOI.Utilities.SingleVariableConstraints{Int}() + x = MOI.add_variable(a) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x00], + Int[0], + Int[0], + ) + f = MOI.SingleVariable(x) c = MOI.add_constraint(a, f, MOI.GreaterThan(3)) - @test a == MOI.Utilities.Box{Int}([0x02], Int[3], Int[0]) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x02], + Int[3], + Int[0], + ) MOI.set(a, MOI.ConstraintSet(), c, MOI.GreaterThan(2)) - @test a == MOI.Utilities.Box{Int}([0x02], Int[2], Int[0]) + @test a == MOI.Utilities.SingleVariableConstraints{Int}( + nothing, + [0x02], + Int[2], + Int[0], + ) return end function test_NumberOfConstraints() - b = MOI.Utilities.Box( + b = MOI.Utilities.SingleVariableConstraints( + nothing, [0x08, 0x02, 0x04, 0x06], [1.0, 3.0, -Inf, -1.0], [2.0, Inf, 4.0, 1.0], @@ -166,7 +209,8 @@ function test_NumberOfConstraints() end function test_ListOfConstraintTypesPresent() - b = MOI.Utilities.Box( + b = MOI.Utilities.SingleVariableConstraints( + nothing, [0x08, 0x02, 0x04, 0x06], [1.0, 3.0, -Inf, -1.0], [2.0, Inf, 4.0, 1.0], @@ -180,7 +224,8 @@ function test_ListOfConstraintTypesPresent() end function test_ListOfConstraintIndices() - b = MOI.Utilities.Box( + b = MOI.Utilities.SingleVariableConstraints{Float64}( + nothing, [0x08, 0x02, 0x04, 0x06], [1.0, 3.0, -Inf, -1.0], [2.0, Inf, 4.0, 1.0], @@ -198,35 +243,56 @@ function test_ListOfConstraintIndices() return end +### +### MatrixBounds +### + +function test_MatrixBounds_equal() + a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) + b = MOI.Utilities.MatrixBounds([1.0, 2.0], [3.0, 4.0]) + c = MOI.Utilities.MatrixBounds([1.0, 3.0], [3.0, 4.0]) + d = MOI.Utilities.MatrixBounds([1.0, 2.0], [3.0, 5.0]) + @test a == a + @test a == b + @test a != c + @test a != d + return +end + +function test_MatrixBounds_empty() + a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) + empty!(a) + @test a == MOI.Utilities.MatrixBounds{Int}() + return +end + +function test_MatrixBounds_resize() + a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) + @test length(a.lower) == 2 + @test length(a.upper) == 2 + resize!(a, 4) + @test length(a.lower) == 4 + @test length(a.upper) == 4 + return +end + function test_load_constants() - a = MOI.Utilities.Box( - [0x00, 0x00, 0x00], - [-Inf, -Inf, -Inf], - [Inf, Inf, Inf], - ) + a = MOI.Utilities.MatrixBounds([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) MOI.Utilities.load_constants(a, 0, MOI.Interval(1.0, 2.0)) MOI.Utilities.load_constants(a, 1, MOI.GreaterThan(3.0)) MOI.Utilities.load_constants(a, 2, MOI.LessThan(4.0)) - @test a == MOI.Utilities.Box( - [0x08, 0x02, 0x04], - [1.0, 3.0, -Inf], - [2.0, Inf, 4.0], - ) + @test a == MOI.Utilities.MatrixBounds([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) return end function test_function_constants() - a = MOI.Utilities.Box( - [0x00, 0x00, 0x00], - [-Inf, -Inf, -Inf], - [Inf, Inf, Inf], - ) + a = MOI.Utilities.MatrixBounds([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) MOI.Utilities.function_constants(a, 0) == 0.0 return end function test_set_from_constants() - a = MOI.Utilities.Box([0x00, 0x00, 0x00], [1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + a = MOI.Utilities.MatrixBounds([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) @test MOI.Utilities.set_from_constants(a, MOI.Interval{Float64}, 1) == MOI.Interval(1.0, 2.0) @test MOI.Utilities.set_from_constants(a, MOI.GreaterThan{Float64}, 2) == From f59a230236d58ed861531355fc5b2d0cc1c8d279 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 19 Jul 2021 18:49:13 +1200 Subject: [PATCH 09/12] Remove variable_indices field --- src/Utilities/model.jl | 1 - src/Utilities/vector_bounds.jl | 106 +++++++++++++++----------------- test/Utilities/vector_bounds.jl | 93 ++++++++-------------------- 3 files changed, 73 insertions(+), 127 deletions(-) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index ef015b285a..78c8875632 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -73,7 +73,6 @@ function _delete_variable( model::AbstractModel{T}, vi::MOI.VariableIndex, ) where {T} - MOI.throw_if_not_valid(model, vi) MOI.delete(model.variable_bounds, vi) model.name_to_var = nothing delete!(model.var_to_name, vi) diff --git a/src/Utilities/vector_bounds.jl b/src/Utilities/vector_bounds.jl index 44f2fc3d27..6e11e49ecf 100644 --- a/src/Utilities/vector_bounds.jl +++ b/src/Utilities/vector_bounds.jl @@ -58,37 +58,39 @@ const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{ MOI.Semiinteger{T}, } -# 0xcb = 0x80 | 0x40 | 0x8 | 0x2 | 0x1 -const _LOWER_BOUND_MASK = 0xcb -# 0xcd = 0x80 | 0x40 | 0x8 | 0x4 | 0x1 -const _UPPER_BOUND_MASK = 0xcd - -_single_variable_flag(::Type{<:MOI.EqualTo}) = 0x1 -_single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x2 -_single_variable_flag(::Type{<:MOI.LessThan}) = 0x4 -_single_variable_flag(::Type{<:MOI.Interval}) = 0x8 -_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 - -function _flag_to_set_type(flag::UInt8, ::Type{T}) where {T} - if flag == 0x1 +# 0xcb = 0x0080 | 0x0040 | 0x0008 | 0x0002 | 0x0001 +const _LOWER_BOUND_MASK = 0x00cb +# 0xcd = 0x0080 | 0x0040 | 0x0008 | 0x0004 | 0x0001 +const _UPPER_BOUND_MASK = 0x00cd + +const _DELETED_VARIABLE = 0x8000 + +_single_variable_flag(::Type{<:MOI.EqualTo}) = 0x0001 +_single_variable_flag(::Type{<:MOI.GreaterThan}) = 0x0002 +_single_variable_flag(::Type{<:MOI.LessThan}) = 0x0004 +_single_variable_flag(::Type{<:MOI.Interval}) = 0x0008 +_single_variable_flag(::Type{MOI.Integer}) = 0x0010 +_single_variable_flag(::Type{MOI.ZeroOne}) = 0x0020 +_single_variable_flag(::Type{<:MOI.Semicontinuous}) = 0x0040 +_single_variable_flag(::Type{<:MOI.Semiinteger}) = 0x0080 + +function _flag_to_set_type(flag::UInt16, ::Type{T}) where {T} + if flag == 0x0001 return MOI.EqualTo{T} - elseif flag == 0x2 + elseif flag == 0x0002 return MOI.GreaterThan{T} - elseif flag == 0x4 + elseif flag == 0x0004 return MOI.LessThan{T} - elseif flag == 0x8 + elseif flag == 0x0008 return MOI.Interval{T} - elseif flag == 0x10 + elseif flag == 0x0010 return MOI.Integer - elseif flag == 0x20 + elseif flag == 0x0020 return MOI.ZeroOne - elseif flag == 0x40 + elseif flag == 0x0040 return MOI.Semicontinuous{T} else - @assert flag == 0x80 + @assert flag == 0x0080 return MOI.Semiinteger{T} end end @@ -159,8 +161,7 @@ _no_upper_bound(::Type{T}) where {T<:AbstractFloat} = typemax(T) """ struct SingleVariableConstraints{T} <: AbstractVectorBounds - variable_indices::Union{Nothing,Set{MOI.VariableIndex}} - set_mask::Vector{UInt8} + set_mask::Vector{UInt16} lower::Vector{T} upper::Vector{T} end @@ -168,25 +169,26 @@ _no_upper_bound(::Type{T}) where {T<:AbstractFloat} = typemax(T) A struct for storing SingleVariable-related constraints. Used in `MOI.Model`. """ mutable struct SingleVariableConstraints{T} <: AbstractVectorBounds - variable_indices::Union{Nothing,Set{MOI.VariableIndex}} - set_mask::Vector{UInt8} + set_mask::Vector{UInt16} lower::Vector{T} upper::Vector{T} end function SingleVariableConstraints{T}() where {T} - return SingleVariableConstraints{T}(nothing, UInt8[], T[], T[]) + return SingleVariableConstraints{T}(UInt16[], T[], T[]) +end + +function MOI.throw_if_not_valid(b::SingleVariableConstraints, index) + if !MOI.is_valid(b, index) + throw(InvalidIndex(index)) + end end function Base.:(==)(a::SingleVariableConstraints, b::SingleVariableConstraints) - return a.variable_indices == a.variable_indices && - a.set_mask == b.set_mask && - a.lower == b.lower && - a.upper == b.upper + return a.set_mask == b.set_mask && a.lower == b.lower && a.upper == b.upper end function Base.empty!(b::SingleVariableConstraints) - b.variable_indices = nothing empty!(b.set_mask) empty!(b.lower) empty!(b.upper) @@ -201,37 +203,30 @@ function Base.resize!(b::SingleVariableConstraints, n) end function MOI.add_variable(b::SingleVariableConstraints{T}) where {T} - push!(b.set_mask, 0x00) + push!(b.set_mask, 0x0000) push!(b.lower, _no_lower_bound(T)) push!(b.upper, _no_upper_bound(T)) x = MOI.VariableIndex(length(b.set_mask)) - if b.variable_indices !== nothing - push!(b.variable_indices, x) - end return x end function MOI.get(b::SingleVariableConstraints, ::MOI.ListOfVariableIndices) - if b.variable_indices === nothing - return MOI.VariableIndex.(1:length(b.set_mask)) - end - x = collect(b.variable_indices) - sort!(x, by = x -> x.value) - return x + return MOI.VariableIndex[ + MOI.VariableIndex(i) for + i in 1:length(b.set_mask) if b.set_mask[i] != _DELETED_VARIABLE + ] end function MOI.is_valid(b::SingleVariableConstraints, x::MOI.VariableIndex) - if b.variable_indices === nothing - return 1 <= x.value <= length(b.set_mask) - end - return x in b.variable_indices + mask = get(b.set_mask, x.value, _DELETED_VARIABLE) + return mask != _DELETED_VARIABLE end function MOI.get(b::SingleVariableConstraints, ::MOI.NumberOfVariables)::Int64 - if b.variable_indices === nothing - return length(b.set_mask) + if length(b.set_mask) == 0 + return 0 end - return length(b.variable_indices) + return sum(x != _DELETED_VARIABLE for x in b.set_mask) end function MOI.add_constraint( @@ -257,6 +252,7 @@ function MOI.delete( b::SingleVariableConstraints{T}, ci::MOI.ConstraintIndex{MOI.SingleVariable,S}, ) where {T,S} + MOI.throw_if_not_valid(b, ci) flag = _single_variable_flag(S) b.set_mask[ci.value] &= ~flag if !iszero(flag & _LOWER_BOUND_MASK) @@ -269,12 +265,8 @@ function MOI.delete( end function MOI.delete(b::SingleVariableConstraints, x::MOI.VariableIndex) - # To "delete" the variable, set it to 0x00 (free). - b.set_mask[x.value] = 0x00 - if b.variable_indices === nothing - b.variable_indices = Set(MOI.get(b, MOI.ListOfVariableIndices())) - end - delete!(b.variable_indices, x) + MOI.throw_if_not_valid(b, x) + b.set_mask[x.value] = _DELETED_VARIABLE return end @@ -317,7 +309,7 @@ function _add_constraint_type( b::SingleVariableConstraints, S::Type{<:MOI.AbstractScalarSet}, ) - flag = _single_variable_flag(S)::UInt8 + flag = _single_variable_flag(S)::UInt16 if any(mask -> !iszero(flag & mask), b.set_mask) push!(list, (MOI.SingleVariable, S)) end diff --git a/test/Utilities/vector_bounds.jl b/test/Utilities/vector_bounds.jl index e7e412b0ad..cd429561a8 100644 --- a/test/Utilities/vector_bounds.jl +++ b/test/Utilities/vector_bounds.jl @@ -17,8 +17,7 @@ end function test_empty() a = MOI.Utilities.SingleVariableConstraints( - Set([MOI.VariableIndex(1)]), - [0x00, 0x00], + [0x0000, 0x0000], [1, 2], [3, 4], ) @@ -29,8 +28,7 @@ end function test_resize() a = MOI.Utilities.SingleVariableConstraints( - nothing, - [0x00, 0x00], + [0x0000, 0x0000], [1, 2], [3, 4], ) @@ -47,17 +45,11 @@ end function test_add_variable() a = MOI.Utilities.SingleVariableConstraints{Int}() MOI.add_variable(a) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x00], - [0], - [0], - ) + @test a == MOI.Utilities.SingleVariableConstraints{Int}([0x0000], [0], [0]) a = MOI.Utilities.SingleVariableConstraints{Float64}() MOI.add_variable(a) @test a == MOI.Utilities.SingleVariableConstraints{Float64}( - nothing, - [0x00], + [0x0000], [-Inf], [Inf], ) @@ -66,36 +58,22 @@ end function test__flag_to_set_type() T = Int - @test_throws AssertionError MOI.Utilities._flag_to_set_type(0x11, T) - @test MOI.Utilities._flag_to_set_type(0x10, T) == MOI.Integer - @test MOI.Utilities._flag_to_set_type(0x20, T) == MOI.ZeroOne + @test_throws AssertionError MOI.Utilities._flag_to_set_type(0x0011, T) + @test MOI.Utilities._flag_to_set_type(0x0010, T) == MOI.Integer + @test MOI.Utilities._flag_to_set_type(0x0020, T) == MOI.ZeroOne return end function test_add_constraint() a = MOI.Utilities.SingleVariableConstraints{Int}() x = MOI.add_variable(a) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x00], - Int[0], - Int[0], - ) + @test a == + MOI.Utilities.SingleVariableConstraints{Int}([0x0000], Int[0], Int[0]) f = MOI.SingleVariable(x) MOI.add_constraint(a, f, MOI.GreaterThan(3)) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x02], - [3], - [0], - ) + @test a == MOI.Utilities.SingleVariableConstraints{Int}([0x0002], [3], [0]) MOI.add_constraint(a, f, MOI.LessThan(4)) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x06], - [3], - [4], - ) + @test a == MOI.Utilities.SingleVariableConstraints{Int}([0x0006], [3], [4]) return end @@ -137,21 +115,13 @@ end function test_delete_variable() a = MOI.Utilities.SingleVariableConstraints{Int}() x = MOI.add_variable(a) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x00], - Int[0], - Int[0], - ) + @test a == + MOI.Utilities.SingleVariableConstraints{Int}([0x0000], Int[0], Int[0]) f = MOI.SingleVariable(x) c = MOI.add_constraint(a, f, MOI.LessThan(3)) MOI.delete(a, x) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - Set{MOI.VariableIndex}(), - [0x00], - Int[0], - Int[3], - ) + @test a == + MOI.Utilities.SingleVariableConstraints{Int}([0x8000], Int[0], Int[3]) return end @@ -169,34 +139,21 @@ end function test_set_ConstraintSet() a = MOI.Utilities.SingleVariableConstraints{Int}() x = MOI.add_variable(a) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x00], - Int[0], - Int[0], - ) + @test a == + MOI.Utilities.SingleVariableConstraints{Int}([0x0000], Int[0], Int[0]) f = MOI.SingleVariable(x) c = MOI.add_constraint(a, f, MOI.GreaterThan(3)) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x02], - Int[3], - Int[0], - ) + @test a == + MOI.Utilities.SingleVariableConstraints{Int}([0x0002], Int[3], Int[0]) MOI.set(a, MOI.ConstraintSet(), c, MOI.GreaterThan(2)) - @test a == MOI.Utilities.SingleVariableConstraints{Int}( - nothing, - [0x02], - Int[2], - Int[0], - ) + @test a == + MOI.Utilities.SingleVariableConstraints{Int}([0x0002], Int[2], Int[0]) return end function test_NumberOfConstraints() b = MOI.Utilities.SingleVariableConstraints( - nothing, - [0x08, 0x02, 0x04, 0x06], + [0x0008, 0x0002, 0x0004, 0x0006], [1.0, 3.0, -Inf, -1.0], [2.0, Inf, 4.0, 1.0], ) @@ -210,8 +167,7 @@ end function test_ListOfConstraintTypesPresent() b = MOI.Utilities.SingleVariableConstraints( - nothing, - [0x08, 0x02, 0x04, 0x06], + [0x0008, 0x0002, 0x0004, 0x0006], [1.0, 3.0, -Inf, -1.0], [2.0, Inf, 4.0, 1.0], ) @@ -225,8 +181,7 @@ end function test_ListOfConstraintIndices() b = MOI.Utilities.SingleVariableConstraints{Float64}( - nothing, - [0x08, 0x02, 0x04, 0x06], + [0x0008, 0x0002, 0x0004, 0x0006], [1.0, 3.0, -Inf, -1.0], [2.0, Inf, 4.0, 1.0], ) From 3f13c18b798895e5133999b5977a33f390b80a22 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 20 Jul 2021 11:45:24 +1200 Subject: [PATCH 10/12] Fix empty --- src/Utilities/model.jl | 6 ++++-- src/Utilities/vector_bounds.jl | 9 ++++++++- test/Utilities/vector_bounds.jl | 7 ++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 78c8875632..bb7d389217 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -561,15 +561,17 @@ function MOI.is_empty(model::AbstractModel) !model.objectiveset && isempty(model.objective.terms) && iszero(model.objective.constant) && - MOI.is_empty(model.constraints) + MOI.is_empty(model.constraints) && + MOI.is_empty(model.variable_bounds) 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}) - empty!(model.variable_bounds) + MOI.empty!(model.variable_bounds) empty!(model.var_to_name) model.name_to_var = nothing empty!(model.con_to_name) diff --git a/src/Utilities/vector_bounds.jl b/src/Utilities/vector_bounds.jl index 6e11e49ecf..b656d6bbe4 100644 --- a/src/Utilities/vector_bounds.jl +++ b/src/Utilities/vector_bounds.jl @@ -188,13 +188,20 @@ function Base.:(==)(a::SingleVariableConstraints, b::SingleVariableConstraints) return a.set_mask == b.set_mask && a.lower == b.lower && a.upper == b.upper end -function Base.empty!(b::SingleVariableConstraints) +function MOI.empty!(b::SingleVariableConstraints) empty!(b.set_mask) empty!(b.lower) empty!(b.upper) return b end +function MOI.is_empty(b::SingleVariableConstraints) + if length(b.set_mask) == 0 + return true + end + return all(isequal(_DELETED_VARIABLE), b.set_mask) +end + function Base.resize!(b::SingleVariableConstraints, n) resize!(b.set_mask, n) resize!(b.lower, n) diff --git a/test/Utilities/vector_bounds.jl b/test/Utilities/vector_bounds.jl index cd429561a8..d8c3614fa1 100644 --- a/test/Utilities/vector_bounds.jl +++ b/test/Utilities/vector_bounds.jl @@ -21,8 +21,8 @@ function test_empty() [1, 2], [3, 4], ) - empty!(a) - @test a == MOI.Utilities.SingleVariableConstraints{Int}() + MOI.empty!(a) + @test MOI.is_empty(a) return end @@ -217,7 +217,8 @@ end function test_MatrixBounds_empty() a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) empty!(a) - @test a == MOI.Utilities.MatrixBounds{Int}() + @test a.lower == Int[] + @test a.upper == Int[] return end From 0ab09f5aa4b6ddb6807bb2f83a6b938494cc86f4 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 21 Jul 2021 12:04:06 +1200 Subject: [PATCH 11/12] Fix typo --- src/Utilities/vector_bounds.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/vector_bounds.jl b/src/Utilities/vector_bounds.jl index b656d6bbe4..db9a4a94a3 100644 --- a/src/Utilities/vector_bounds.jl +++ b/src/Utilities/vector_bounds.jl @@ -180,7 +180,7 @@ end function MOI.throw_if_not_valid(b::SingleVariableConstraints, index) if !MOI.is_valid(b, index) - throw(InvalidIndex(index)) + throw(MOI.InvalidIndex(index)) end end From 06ebf24b034d9ff2c8f82c2b37cba24b92a24eb0 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 21 Jul 2021 14:28:06 +1200 Subject: [PATCH 12/12] MatrixBounds -> Hyperrectangle --- docs/src/submodules/Utilities/overview.md | 2 +- docs/src/submodules/Utilities/reference.md | 2 +- src/Utilities/vector_bounds.jl | 20 ++++++++-------- test/Utilities/matrix_of_constraints.jl | 20 ++++++++-------- test/Utilities/vector_bounds.jl | 28 +++++++++++----------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/src/submodules/Utilities/overview.md b/docs/src/submodules/Utilities/overview.md index ddb574f483..80a0084fb4 100644 --- a/docs/src/submodules/Utilities/overview.md +++ b/docs/src/submodules/Utilities/overview.md @@ -410,7 +410,7 @@ const Model = MOI.Utilities.GenericModel{ MOI.Utilities.MatrixOfConstraints{ Float64, MOI.Utilities.MutableSparseMatrixCSC{Float64,Cint,MOI.Utilities.ZeroBasedIndexing}, - MOI.Utilities.MatrixBounds{Float64}, + MOI.Utilities.Hyperrectangle{Float64}, LP{Float64}, }, } diff --git a/docs/src/submodules/Utilities/reference.md b/docs/src/submodules/Utilities/reference.md index 17b9caf426..9f6a7f7dc4 100644 --- a/docs/src/submodules/Utilities/reference.md +++ b/docs/src/submodules/Utilities/reference.md @@ -113,7 +113,7 @@ Utilities.set_from_constants ``` ```@docs -Utilities.MatrixBounds +Utilities.Hyperrectangle ``` ### `.sets` diff --git a/src/Utilities/vector_bounds.jl b/src/Utilities/vector_bounds.jl index db9a4a94a3..0d254a2d98 100644 --- a/src/Utilities/vector_bounds.jl +++ b/src/Utilities/vector_bounds.jl @@ -45,7 +45,7 @@ end SUPPORTED_VARIABLE_SCALAR_SETS{T} The union of scalar sets for `SingleVariable` constraints supported by -`Utilities.MatrixBounds` and `Utilities.SingleVariableConstraints`. +`Utilities.Hyperrectangle` and `Utilities.SingleVariableConstraints`. """ const SUPPORTED_VARIABLE_SCALAR_SETS{T} = Union{ MOI.EqualTo{T}, @@ -354,42 +354,42 @@ function MOI.get( end ### -### MatrixBounds +### Hyperrectangle ### """ - struct MatrixBounds{T} <: AbstractVectorBounds + struct Hyperrectangle{T} <: AbstractVectorBounds lower::Vector{T} upper::Vector{T} end A struct for the .constants field in MatrixOfConstraints. """ -struct MatrixBounds{T} <: AbstractVectorBounds +struct Hyperrectangle{T} <: AbstractVectorBounds lower::Vector{T} upper::Vector{T} end -MatrixBounds{T}() where {T} = MatrixBounds{T}(T[], T[]) +Hyperrectangle{T}() where {T} = Hyperrectangle{T}(T[], T[]) -function Base.:(==)(a::MatrixBounds, b::MatrixBounds) +function Base.:(==)(a::Hyperrectangle, b::Hyperrectangle) return a.lower == b.lower && a.upper == b.upper end -function Base.empty!(b::MatrixBounds) +function Base.empty!(b::Hyperrectangle) empty!(b.lower) empty!(b.upper) return b end -function Base.resize!(b::MatrixBounds, n) +function Base.resize!(b::Hyperrectangle, n) resize!(b.lower, n) resize!(b.upper, n) return end function load_constants( - b::MatrixBounds{T}, + b::Hyperrectangle{T}, offset, set::SUPPORTED_VARIABLE_SCALAR_SETS{T}, ) where {T} @@ -407,4 +407,4 @@ function load_constants( return end -function_constants(::MatrixBounds{T}, row) where {T} = zero(T) +function_constants(::Hyperrectangle{T}, row) where {T} = zero(T) diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index 13d81c86c5..30663b1ae8 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -192,18 +192,18 @@ function test_contlinear() end function test_contlinear(Indexing) A2 = sparse([1, 1], [1, 2], ones(2)) - b2 = MOI.Utilities.MatrixBounds([-Inf], [1.0]) + b2 = MOI.Utilities.Hyperrectangle([-Inf], [1.0]) Alp = sparse( [1, 1, 2, 3, 4, 4], [1, 2, 1, 2, 1, 2], Float64[3, 2, 1, -1, 5, -4], ) - blp = MOI.Utilities.MatrixBounds([5, 0, -Inf, 6], [5, Inf, 0, 7]) + blp = MOI.Utilities.Hyperrectangle([5, 0, -Inf, 6], [5, Inf, 0, 7]) F = MOI.ScalarAffineFunction{Float64} @testset "$SetType" for SetType in [MixLP{Float64}, OrdLP{Float64}] _test( MOIT.linear2test, - MOI.Utilities.MatrixBounds{Float64}, + MOI.Utilities.Hyperrectangle{Float64}, SetType, A2, b2, @@ -223,7 +223,7 @@ function test_contlinear(Indexing) end _test( _lp, - MOI.Utilities.MatrixBounds{Float64}, + MOI.Utilities.Hyperrectangle{Float64}, SetType, Alp, blp, @@ -363,7 +363,7 @@ end function test_get_by_name(T::Type, SetsType::Type) model = matrix_instance( T, - MOI.Utilities.MatrixBounds{T}, + MOI.Utilities.Hyperrectangle{T}, SetsType, MOI.Utilities.OneBasedIndexing, ) @@ -393,7 +393,7 @@ MOIU.@struct_of_constraints_by_function_types( function test_nametest() T = Float64 Indexing = MOIU.OneBasedIndexing - ConstantsType = MOIU.MatrixBounds{T} + ConstantsType = MOIU.Hyperrectangle{T} for ProductOfSetsType in [MixLP{Float64}, OrdLP{Float64}] model = MOIU.GenericOptimizer{ T, @@ -443,7 +443,7 @@ end function test_valid() T = Float64 Indexing = MOIU.OneBasedIndexing - ConstantsType = MOIU.MatrixBounds{T} + ConstantsType = MOIU.Hyperrectangle{T} for ProductOfSetsType in [MixLP{Float64}, OrdLP{Float64}] model = matrix_instance(T, ConstantsType, ProductOfSetsType, Indexing) MOI.DeprecatedTest.validtest(model, delete = false) @@ -452,7 +452,7 @@ end function test_supports_constraint(T::Type = Float64, BadT::Type = Float32) Indexing = MOIU.OneBasedIndexing - ConstantsType = MOIU.MatrixBounds{T} + ConstantsType = MOIU.Hyperrectangle{T} for ProductOfSetsType in [MixLP{Float64}, OrdLP{Float64}] model = MOIU.GenericOptimizer{ T, @@ -490,7 +490,7 @@ function test_copy(Indexing) MOIU.MatrixOfConstraints{ T, MOIU.MutableSparseMatrixCSC{T,Int,Indexing}, - MOIU.MatrixBounds{T}, + MOIU.Hyperrectangle{T}, ScalarSetsType, }, MOIU.MatrixOfConstraints{ @@ -513,7 +513,7 @@ end function test_modif() model = matrix_instance( Int, - MOIU.MatrixBounds{Int}, + MOIU.Hyperrectangle{Int}, OrdLP{Int}, MOIU.OneBasedIndexing, ) diff --git a/test/Utilities/vector_bounds.jl b/test/Utilities/vector_bounds.jl index d8c3614fa1..5c2633274a 100644 --- a/test/Utilities/vector_bounds.jl +++ b/test/Utilities/vector_bounds.jl @@ -199,14 +199,14 @@ function test_ListOfConstraintIndices() end ### -### MatrixBounds +### Hyperrectangle ### -function test_MatrixBounds_equal() - a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) - b = MOI.Utilities.MatrixBounds([1.0, 2.0], [3.0, 4.0]) - c = MOI.Utilities.MatrixBounds([1.0, 3.0], [3.0, 4.0]) - d = MOI.Utilities.MatrixBounds([1.0, 2.0], [3.0, 5.0]) +function test_Hyperrectangle_equal() + a = MOI.Utilities.Hyperrectangle([1, 2], [3, 4]) + b = MOI.Utilities.Hyperrectangle([1.0, 2.0], [3.0, 4.0]) + c = MOI.Utilities.Hyperrectangle([1.0, 3.0], [3.0, 4.0]) + d = MOI.Utilities.Hyperrectangle([1.0, 2.0], [3.0, 5.0]) @test a == a @test a == b @test a != c @@ -214,16 +214,16 @@ function test_MatrixBounds_equal() return end -function test_MatrixBounds_empty() - a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) +function test_Hyperrectangle_empty() + a = MOI.Utilities.Hyperrectangle([1, 2], [3, 4]) empty!(a) @test a.lower == Int[] @test a.upper == Int[] return end -function test_MatrixBounds_resize() - a = MOI.Utilities.MatrixBounds([1, 2], [3, 4]) +function test_Hyperrectangle_resize() + a = MOI.Utilities.Hyperrectangle([1, 2], [3, 4]) @test length(a.lower) == 2 @test length(a.upper) == 2 resize!(a, 4) @@ -233,22 +233,22 @@ function test_MatrixBounds_resize() end function test_load_constants() - a = MOI.Utilities.MatrixBounds([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) + a = MOI.Utilities.Hyperrectangle([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) MOI.Utilities.load_constants(a, 0, MOI.Interval(1.0, 2.0)) MOI.Utilities.load_constants(a, 1, MOI.GreaterThan(3.0)) MOI.Utilities.load_constants(a, 2, MOI.LessThan(4.0)) - @test a == MOI.Utilities.MatrixBounds([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + @test a == MOI.Utilities.Hyperrectangle([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) return end function test_function_constants() - a = MOI.Utilities.MatrixBounds([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) + a = MOI.Utilities.Hyperrectangle([-Inf, -Inf, -Inf], [Inf, Inf, Inf]) MOI.Utilities.function_constants(a, 0) == 0.0 return end function test_set_from_constants() - a = MOI.Utilities.MatrixBounds([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) + a = MOI.Utilities.Hyperrectangle([1.0, 3.0, -Inf], [2.0, Inf, 4.0]) @test MOI.Utilities.set_from_constants(a, MOI.Interval{Float64}, 1) == MOI.Interval(1.0, 2.0) @test MOI.Utilities.set_from_constants(a, MOI.GreaterThan{Float64}, 2) ==