diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index de39a929f7..bac6621917 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -30,6 +30,7 @@ include("functions.jl") include("mutable_arithmetics.jl") include("sets.jl") include("constraints.jl") +include("dense_dict.jl") include("copy.jl") include("results.jl") include("variables.jl") diff --git a/src/Utilities/copy.jl b/src/Utilities/copy.jl index b03cc9fbf4..4d1e3a0407 100644 --- a/src/Utilities/copy.jl +++ b/src/Utilities/copy.jl @@ -63,12 +63,31 @@ error in case `copy_to` is called with `copy_names` equal to `true`. """ supports_default_copy_to(model::MOI.ModelLike, copy_names::Bool) = false +const DenseVariableDict{V} = DenseDict{MOI.VariableIndex, V, typeof(MOI.index_value), typeof(MOI.VariableIndex)} +function dense_variable_dict(::Type{V}, n) where V + return DenseDict{MOI.VariableIndex, V}(MOI.index_value, MOI.VariableIndex, n) +end + struct IndexMap <: AbstractDict{MOI.Index, MOI.Index} - varmap::Dict{MOI.VariableIndex, MOI.VariableIndex} + varmap::Union{DenseVariableDict{MOI.VariableIndex}, + Dict{MOI.VariableIndex, MOI.VariableIndex}} conmap::Dict{MOI.ConstraintIndex, MOI.ConstraintIndex} end IndexMap() = IndexMap(Dict{MOI.VariableIndex, MOI.VariableIndex}(), Dict{MOI.ConstraintIndex, MOI.ConstraintIndex}()) +function IndexMap(n) + IndexMap(dense_variable_dict(MOI.VariableIndex, n), + Dict{MOI.ConstraintIndex, MOI.ConstraintIndex}()) +end + +function index_map_for_variable_indices(variables) + n = length(variables) + if all(i -> variables[i] == MOI.VariableIndex(i), 1:n) + return IndexMap(n) + else + return IndexMap() + end +end Base.getindex(idxmap::IndexMap, vi::MOI.VariableIndex) = idxmap.varmap[vi] function Base.getindex(idxmap::IndexMap, ci::MOI.ConstraintIndex{F, S}) where {F, S} @@ -298,9 +317,8 @@ the copying a model incrementally. function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bool) MOI.empty!(dest) - idxmap = IndexMap() - vis_src = MOI.get(src, MOI.ListOfVariableIndices()) + idxmap = index_map_for_variable_indices(vis_src) constraint_types = MOI.get(src, MOI.ListOfConstraints()) single_variable_types = Type{<:MOI.AbstractScalarSet}[] vector_of_variables_types = Type{<:MOI.AbstractVectorSet}[] @@ -659,9 +677,8 @@ the Allocate-Load API. function allocate_load(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bool) MOI.empty!(dest) - idxmap = IndexMap() - vis_src = MOI.get(src, MOI.ListOfVariableIndices()) + idxmap = index_map_for_variable_indices(vis_src) constraint_types = MOI.get(src, MOI.ListOfConstraints()) single_variable_types = [S for (F, S) in constraint_types if F == MOI.SingleVariable] diff --git a/src/Utilities/dense_dict.jl b/src/Utilities/dense_dict.jl new file mode 100644 index 0000000000..a7ac300baf --- /dev/null +++ b/src/Utilities/dense_dict.jl @@ -0,0 +1,41 @@ +""" + struct DenseDict{K, V, F, I} <: AbstractDict{K, V} + hash::F + inverse_hash::I + set::BitSet + map::Vector{V} + end + +Same as `Dict{K, V}` but `hash(key)` is assumed to belong to `eachindex(map)`. +""" +struct DenseDict{K, V, F, I} <: AbstractDict{K, V} + hash::F + inverse_hash::I + set::BitSet + map::Vector{V} + function DenseDict{K, V}(hash, inverse_hash, n) where {K, V} + set = BitSet() + sizehint!(set, n) + return new{K, V, typeof(hash), typeof(inverse_hash)}(hash, inverse_hash, set, Vector{K}(undef, n)) + end +end + +# Implementation of the `AbstractDict` API. +# Base.empty(::DenseDict, ::Type{K}, ::Type{V}) not implemented +function Base.iterate(d::DenseDict, args...) + itr = iterate(d.set, args...) + if itr === nothing + return nothing + else + el, i = itr + return d.inverse_hash(el) => d.map[el], i + end +end +Base.length(d::DenseDict) = length(d.set) +Base.haskey(dict::DenseDict, key) = dict.hash(key) in dict.set +Base.getindex(dict::DenseDict, key) = dict.map[dict.hash(key)] +function Base.setindex!(dict::DenseDict, value, key) + h = dict.hash(key) + push!(dict.set, h) + dict.map[h] = value +end diff --git a/src/indextypes.jl b/src/indextypes.jl index bd7ad7dfa4..82afa66f40 100644 --- a/src/indextypes.jl +++ b/src/indextypes.jl @@ -19,6 +19,7 @@ index.value == MOI.get(model, MOI.ConstraintFunction(), index).variable.value struct ConstraintIndex{F, S} value::Int64 end +index_value(ci::ConstraintIndex) = ci.value """ VariableIndex @@ -29,6 +30,7 @@ To allow for deletion, indices need not be consecutive. struct VariableIndex value::Int64 end +index_value(vi::VariableIndex) = vi.value # The default hash is slow. It's important for the performance of dictionaries # of VariableIndices to define our own. diff --git a/test/Utilities/Utilities.jl b/test/Utilities/Utilities.jl index fc4db83c80..a42ba9e132 100644 --- a/test/Utilities/Utilities.jl +++ b/test/Utilities/Utilities.jl @@ -30,6 +30,9 @@ end @testset "Caching Optimizer" begin include("cachingoptimizer.jl") end +@testset "DenseDict" begin + include("dense_dict.jl") +end @testset "Copy" begin include("copy.jl") end diff --git a/test/Utilities/copy.jl b/test/Utilities/copy.jl index 56f8bd7ff2..142c8622f6 100644 --- a/test/Utilities/copy.jl +++ b/test/Utilities/copy.jl @@ -4,6 +4,7 @@ const MOI = MathOptInterface const MOIT = MOI.Test const MOIU = MOI.Utilities + include("../dummy.jl") remove_moi(x::String) = replace(x, "MathOptInterface." => "") @@ -170,7 +171,7 @@ mutable struct ReverseOrderConstrainedVariablesModel <: AbstractConstrainedVaria inner ::MOIU.Model{Float64} ReverseOrderConstrainedVariablesModel() = new(MOI.ConstraintIndex[], MOIU.Model{Float64}()) end - + MOI.add_variables(model::AbstractConstrainedVariablesModel, n) = MOI.add_variables(model.inner, n) diff --git a/test/Utilities/dense_dict.jl b/test/Utilities/dense_dict.jl new file mode 100644 index 0000000000..14edf1affe --- /dev/null +++ b/test/Utilities/dense_dict.jl @@ -0,0 +1,34 @@ +using Test +import MathOptInterface +const MOI = MathOptInterface + +mul2(x) = x * 2 +div2(x) = div(x, 2) +d = MOI.Utilities.DenseDict{Int, Float64}(div2, mul2, 3) + +d[4] = 0.25 +@test !haskey(d, 2) +@test haskey(d, 4) +@test !haskey(d, 6) +@test length(d) == 1 +@test collect(d) == [4 => 0.25] +@test d[4] == 0.25 + +d[2] = 1.5 +@test haskey(d, 2) +@test haskey(d, 4) +@test !haskey(d, 6) +@test length(d) == 2 +@test collect(d) == [2 => 1.5, 4 => 0.25] +@test d[2] == 1.5 +@test d[4] == 0.25 + +d[6] = 0.75 +@test haskey(d, 2) +@test haskey(d, 4) +@test haskey(d, 6) +@test length(d) == 3 +@test collect(d) == [2 => 1.5, 4 => 0.25, 6 => 0.75] +@test d[2] == 1.5 +@test d[4] == 0.25 +@test d[6] == 0.75