diff --git a/Project.toml b/Project.toml index e02a6b7ca..659ba2cba 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RandomNumbers = "e6cf234a-135c-5ec9-84dd-332b85af5143" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" @@ -24,7 +25,7 @@ TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" BlockDecomposition = "1.10" Crayons = "4.1" DataStructures = "0.17, 0.18" -DynamicSparseArrays = "0.5.3" +DynamicSparseArrays = "0.6" MathOptInterface = "0.10, 1" Parameters = "0.12" RandomNumbers = "1.5" diff --git a/src/Algorithm/benders.jl b/src/Algorithm/benders.jl index 1e29d1994..61e4c908f 100644 --- a/src/Algorithm/benders.jl +++ b/src/Algorithm/benders.jl @@ -105,7 +105,7 @@ function update_benders_sp_problem!( for (varid, var) in getvars(spform) iscuractive(spform, varid) || continue getduty(varid) <= BendSpSlackFirstStageVar || continue - haskey(master_primal_sol, varid) || continue + !iszero(master_primal_sol[varid]) || continue setcurub!(spform, var, getperenub(spform, var) - master_primal_sol[varid]) end @@ -428,12 +428,14 @@ function generatecuts!( )::Tuple{Int, Bool, PrimalBound} masterform = getmaster(reform) S = getobjsense(masterform) - filtered_dual_sol = filter(elem -> getduty(elem[1]) == MasterPureConstr, master_dual_sol) + + # following variable is not used: + #filtered_dual_sol = filter(elem -> getduty(elem[1]) == MasterPureConstr, master_dual_sol) ## TODO stabilization : move the following code inside a loop nb_new_cuts, spsols_relaxed, pb_correction, sp_pb_contrib = solve_sps_to_gencuts!( - algo, env, algdata, reform, master_primal_sol, filtered_dual_sol, phase + algo, env, algdata, reform, master_primal_sol, master_dual_sol, phase ) update_lagrangian_pb!(algdata, reform, master_dual_sol, sp_pb_contrib) if nb_new_cuts < 0 diff --git a/src/ColunaBase/ColunaBase.jl b/src/ColunaBase/ColunaBase.jl index 94cfac5c3..286c55b86 100644 --- a/src/ColunaBase/ColunaBase.jl +++ b/src/ColunaBase/ColunaBase.jl @@ -2,7 +2,7 @@ module ColunaBase using ..Coluna -using DynamicSparseArrays, MathOptInterface, TimerOutputs, RandomNumbers, Random +using DynamicSparseArrays, MathOptInterface, TimerOutputs, RandomNumbers, Random, SparseArrays const MOI = MathOptInterface const TO = TimerOutputs diff --git a/src/ColunaBase/hashtable.jl b/src/ColunaBase/hashtable.jl index a461677b7..7176d3ca6 100644 --- a/src/ColunaBase/hashtable.jl +++ b/src/ColunaBase/hashtable.jl @@ -5,35 +5,57 @@ const MT_MASK = 0x0ffff # hash keys from 1 to 65536 This datastructure allows us to quickly find solution that shares the same members: variables for primal solutions and constraints for dual solutions. """ -struct HashTable{VarConstrId} +struct HashTable{MemberId,SolId} rng::MersenneTwisters.MT19937 - memberid_to_hash::Dict{VarConstrId, UInt32} # members of the primal/dual solution -> hash - hash_to_solids::Vector{Vector{VarConstrId}} # hash of the primal/dual solution -> solution id + memberid_to_hash::Dict{MemberId, UInt32} # members of the primal/dual solution -> hash + hash_to_solids::Vector{Vector{SolId}} # hash of the primal/dual solution -> solution id - HashTable{VarConstrId}() where {VarConstrId} = new( + HashTable{MemberId,SolId}() where {MemberId,SolId} = new( MersenneTwisters.MT19937(MT_SEED), - Dict{VarConstrId, UInt32}(), - [VarConstrId[] for _ in 0:MT_MASK] + Dict{MemberId, UInt32}(), + [SolId[] for _ in 0:MT_MASK] ) end +function _gethash!( + hashtable::HashTable{MemberId,SolId}, id::MemberId, bad_hash = Int(MT_MASK) + 2 +) where {MemberId,SolId} + hash = UInt32(get(hashtable.memberid_to_hash, id, bad_hash) - 1) + if hash > MT_MASK + hash = MersenneTwisters.mt_get(hashtable.rng) & MT_MASK + hashtable.memberid_to_hash[id] = Int(hash) + 1 + end + return hash +end + +_gethash!(hashtable, entry::Tuple, bad_hash = Int(MT_MASK) + 2) = + _gethash!(hashtable, first(entry), bad_hash) + +# By default, we consider that the iterator of the `sol` argument returns a tuple that +# contains the id as first element. function gethash(hashtable::HashTable, sol) - bad_hash = Int(MT_MASK) + 2 acum_hash = UInt32(0) - for (varconstrid, _) in sol - hash = UInt32(get(hashtable.memberid_to_hash, varconstrid, bad_hash) - 1) - if hash > MT_MASK - hash = MersenneTwisters.mt_get(hashtable.rng) & MT_MASK - hashtable.memberid_to_hash[varconstrid] = Int(hash) + 1 - end - acum_hash ⊻= hash + for entry in sol + acum_hash ⊻= _gethash!(hashtable, entry) + end + return Int(acum_hash) + 1 +end + +# If the solution is in a sparse vector, we just want to check indices associated to non-zero +# values. +function gethash(hashtable::HashTable, sol::SparseVector) + acum_hash = UInt32(0) + for nzid in SparseArrays.nonzeroinds(sol) + acum_hash ⊻= _gethash!(hashtable, nzid) end return Int(acum_hash) + 1 end -savesolid!(hashtable::HashTable, solid, sol) = push!(getsolids(hashtable, sol), solid) +savesolid!(hashtable::HashTable, solid, sol) = + push!(getsolids(hashtable, sol), solid) -getsolids(hashtable::HashTable, sol) = hashtable.hash_to_solids[gethash(hashtable, sol)] +getsolids(hashtable::HashTable, sol) = + hashtable.hash_to_solids[gethash(hashtable, sol)] function Base.show(io::IO, ht::HashTable) println(io, typeof(ht), ":") diff --git a/src/ColunaBase/solsandbounds.jl b/src/ColunaBase/solsandbounds.jl index 0146ad92a..1dc7e24f9 100644 --- a/src/ColunaBase/solsandbounds.jl +++ b/src/ColunaBase/solsandbounds.jl @@ -257,11 +257,11 @@ function convert_status(coluna_status::SolutionStatus) end # Basic structure of a solution -struct Solution{Model<:AbstractModel,Decision,Value} <: AbstractDict{Decision,Value} +struct Solution{Model<:AbstractModel,Decision<:Integer,Value} <: AbstractSparseVector{Decision,Value} model::Model bound::Float64 status::SolutionStatus - sol::DynamicSparseArrays.PackedMemoryArray{Decision,Value} + sol::SparseVector{Value,Decision} end """ @@ -283,10 +283,9 @@ Create a solution to the `model`. Other arguments are: - `status` is the solution status. """ function Solution{Mo,De,Va}( - model::Mo, decisions::Vector{De}, values::Vector{Va}, solution_value::Float64, - status::SolutionStatus + model::Mo, decisions::Vector{De}, values::Vector{Va}, solution_value::Float64, status::SolutionStatus ) where {Mo<:AbstractModel,De,Va} - sol = DynamicSparseArrays.dynamicsparsevec(decisions, values) + sol = sparsevec(decisions, values, typemax(De)) return Solution(model, solution_value, status, sol) end @@ -302,22 +301,56 @@ getvalue(s::Solution) = float(s.bound) "Return the solution status of `solution`." getstatus(s::Solution) = s.status -Base.iterate(s::Solution) = iterate(s.sol) -Base.iterate(s::Solution, state) = iterate(s.sol, state) +# implementing indexing interface +Base.getindex(s::Solution, i::Integer) = getindex(s.sol, i) +Base.setindex!(s::Solution, v, i::Integer) = setindex!(s.sol, v, i) +Base.firstindex(s::Solution) = firstindex(s.sol) +Base.lastindex(s::Solution) = lastindex(s.sol) + +# implementing abstract array interface +Base.size(s::Solution) = size(s.sol) Base.length(s::Solution) = length(s.sol) -Base.get(s::Solution{Mo,De,Va}, id::De, default) where {Mo,De,Va} = s.sol[id] -Base.getindex(s::Solution{Mo,De,Va}, id::De) where {Mo,De,Va} = Base.getindex(s.sol, id) -Base.setindex!(s::Solution{Mo,De,Va}, val::Va, id::De) where {Mo,De,Va} = s.sol[id] = val +Base.IndexStyle(::Type{<:Solution{Mo,De,Va}}) where {Mo,De,Va} = + IndexStyle(SparseVector{Va,De}) +SparseArrays.nnz(s::Solution) = nnz(s.sol) + +# It iterates only on non-zero values because: +# - we use indices (`Id`) that behaves like an Int with additional information and given a +# indice, we cannot deduce the additional information for the next one (i.e. impossible to +# create an Id for next integer); +# - we don't know the length of the vector (it depends on the number of variables & +# constraints that varies over time). +function Base.iterate(s::Solution) + iterator = Iterators.zip(findnz(s.sol)...) + next = iterate(iterator) + isnothing(next) && return nothing + (item, zip_state) = next + return (item, (zip_state, iterator)) +end + +function Base.iterate(::Solution, state) + (zip_state, iterator) = state + next = iterate(iterator, zip_state) + isnothing(next) && return nothing + (next_item, next_zip_state) = next + return (next_item, (next_zip_state, iterator)) +end + +# # implementing sparse array interface +# SparseArrays.nnz(s::Solution) = nnz(s.sol) +# SparseArrays.nonzeroinds(s::Solution) = SparseArrays.nonzeroinds(s.sol) +# SparseArrays.nonzeros(s::Solution) = nonzeros(s.sol) + +function _eq_sparse_vec(a::SparseVector, b::SparseVector) + a_ids, a_vals = findnz(a) + b_ids, b_vals = findnz(b) + return a_ids == b_ids && a_vals == b_vals +end Base.:(==)(::Solution, ::Solution) = false function Base.:(==)(a::S, b::S) where {S<:Solution} return a.model == b.model && a.bound == b.bound && a.status == b.status && - a.sol == b.sol -end - -# TODO : remove when refactoring Benders -function Base.filter(f::Function, s::S) where {S <: Solution} - return S(s.model, s.bound, s.status, filter(f, s.sol)) + _eq_sparse_vec(a.sol, b.sol) end function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va}, valcmp=(==)) where {Mo,De,Va} @@ -328,12 +361,23 @@ function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va}, valcmp=(==)) where {Mo, return false end -function Base.show(io::IO, solution::Solution{Mo,De,Va}) where {Mo,De,Va} +function Base.show(io::IOContext, solution::Solution{Mo,De,Va}) where {Mo,De,Va} println(io, "Solution") for (decision, value) in solution println(io, "| ", decision, " = ", value) end Printf.@printf(io, "└ value = %.2f \n", getvalue(solution)) end + # Todo : revise method Base.copy(s::S) where {S<:Solution} = S(s.bound, copy(s.sol)) + +# Implementing comparison between solution & dynamic matrix col view for solution comparison +function Base.:(==)(v1::DynamicMatrixColView, v2::Solution) + for ((i1,j1), (i2,j2)) in Iterators.zip(v1,v2) + if !(i1 == i2 && j1 == j2) + return false + end + end + return true +end \ No newline at end of file diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index f7f64648b..921e4f3bd 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -9,7 +9,7 @@ using ..ColunaBase import Base: haskey, length, iterate, diff, delete!, contains, setindex!, getindex, view -using DynamicSparseArrays, Logging, Printf +using DynamicSparseArrays, SparseArrays, Logging, Printf const BD = BlockDecomposition const ClB = ColunaBase diff --git a/src/MathProg/duties.jl b/src/MathProg/duties.jl index 592f42e7b..055869c42 100644 --- a/src/MathProg/duties.jl +++ b/src/MathProg/duties.jl @@ -28,7 +28,7 @@ mutable struct DwSp <: AbstractSpDuty ## [colid, varid] = value primalsols_pool::VarVarMatrix # Hash table to quickly find identical solutions - hashtable_primalsols_pool::HashTable{VarId} + hashtable_primalsols_pool::HashTable{VarId,VarId} ## Perennial cost of solutions costs_primalsols_pool::Dict{VarId, Float64} ## Custom representation of solutions @@ -40,7 +40,7 @@ function DwSp(setup_var, lower_multiplicity, upper_multiplicity, column_var_kind return DwSp( setup_var, lower_multiplicity, upper_multiplicity, column_var_kind, dynamicsparse(VarId, VarId, Float64; fill_mode = false), - HashTable{VarId}(), + HashTable{VarId, VarId}(), Dict{VarId, Float64}(), Dict{VarId, BD.AbstractCustomData}() ) diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 8040437c6..3eae15b86 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -255,8 +255,7 @@ get_primal_sol_pool_hash_table(form::Formulation{DwSp}) = form.duty_data.hashtab function _get_same_sol_in_pool(pool_sols, pool_hashtable, sol) sols_with_same_members = getsolids(pool_hashtable, sol) for existing_sol_id in sols_with_same_members - # TODO: implement comparison between view & dynamicsparsevec - existing_sol = pool_sols[existing_sol_id,:] + existing_sol = @view pool_sols[existing_sol_id,:] if existing_sol == sol return existing_sol_id end @@ -316,7 +315,7 @@ function get_column_from_pool(primal_sol::PrimalSolution{Formulation{DwSp}}) spform = primal_sol.solution.model pool = get_primal_sol_pool(spform) pool_hashtable = get_primal_sol_pool_hash_table(spform) - return _get_same_sol_in_pool(pool, pool_hashtable, primal_sol.solution.sol) + return _get_same_sol_in_pool(pool, pool_hashtable, primal_sol) end """ diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index 7ac95511a..5d9375309 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -1,5 +1,3 @@ -const DynSparseVector{I} = DynamicSparseArrays.PackedMemoryArray{I, Float64} - const VarMembership = Dict{VarId, Float64} const ConstrMembership = Dict{ConstrId, Float64} const ConstrConstrMatrix = DynamicSparseArrays.DynamicSparseMatrix{ConstrId,ConstrId,Float64} @@ -7,7 +5,7 @@ const VarConstrDualSolMatrix = DynamicSparseArrays.DynamicSparseMatrix{VarId,Con const VarVarMatrix = DynamicSparseArrays.DynamicSparseMatrix{VarId,VarId,Float64} # Define the semaphore of the dynamic sparse matrix using MathProg.Id as index -DynamicSparseArrays.semaphore_key(::Type{I}) where {I <: Id} = zero(I) +DynamicSparseArrays.semaphore_key(I::Type{Id{VC}}) where VC = I(Duty{VC}(0), -1, -1, -1, -1) # We wrap the coefficient matrix because we need to buffer the changes. struct CoefficientMatrix{C,V,T} @@ -51,7 +49,7 @@ mutable struct FormulationManager coefficients::ConstrVarMatrix # rows = constraints, cols = variables dual_sols::ConstrConstrMatrix # cols = dual solutions with constrid, rows = constrs dual_sols_varbounds::VarConstrDualSolMatrix # cols = dual solutions with constrid, rows = variables - dual_sol_rhss::DynSparseVector{ConstrId} # dual solutions with constrid map to their rhs + dual_sol_rhss::DynamicSparseVector{ConstrId} # dual solutions with constrid map to their rhs robust_constr_generators::Vector{RobustConstraintsGenerator} custom_families_id::Dict{DataType,Int} end diff --git a/src/MathProg/solutions.jl b/src/MathProg/solutions.jl index b4cbb7254..67e289633 100644 --- a/src/MathProg/solutions.jl +++ b/src/MathProg/solutions.jl @@ -102,10 +102,11 @@ ColunaBase.getmodel(s::AbstractSolution) = getmodel(s.solution) ColunaBase.getvalue(s::AbstractSolution) = getvalue(s.solution) ColunaBase.getbound(s::AbstractSolution) = getbound(s.solution) ColunaBase.getstatus(s::AbstractSolution) = getstatus(s.solution) + Base.length(s::AbstractSolution) = length(s.solution) -Base.get(s::AbstractSolution, id, default) = Base.get(s.solution, id, default) -Base.getindex(s::AbstractSolution, id) = Base.getindex(s.solution, id) -Base.setindex!(s::AbstractSolution, val, id) = Base.setindex!(s.solution, val, id) +Base.get(s::AbstractSolution, id, default) = get(s.solution, id, default) +Base.getindex(s::AbstractSolution, id) = getindex(s.solution, id) +Base.setindex!(s::AbstractSolution, val, id) = setindex!(s.solution, val, id) # Iterating over a PrimalSolution or a DualSolution is similar to iterating over # ColunaBase.Solution @@ -191,13 +192,11 @@ function Base.show(io::IO, solution::PrimalSolution{M}) where {M} Printf.@printf(io, "└ value = %.2f \n", getvalue(solution)) end -# Following methods are needed by Benders -# TODO : check if we can remove them during refactoring of Benders -# not performant -Base.haskey(s::AbstractSolution, key) = haskey(s.solution, key) -# we can't filter the constraints, the variables, and the custom data. -function Base.filter(f::Function, s::DualSolution) - return DualSolution( - filter(f, s.solution), s.var_redcosts, s.custom_data - ) -end \ No newline at end of file + +# To check if a solution is part of solutions from the pool. +Base.:(==)(v1::DynamicMatrixColView, v2::AbstractSolution) = v1 == v2.solution + +# To allocate an array with size equals to the number of non-zero elements when using +# "generation" syntax. +Base.length(gen::Base.Generator{<:AbstractSolution}) = nnz(gen.iter.solution) + diff --git a/src/MathProg/vcids.jl b/src/MathProg/vcids.jl index 01b9e60b4..84970a9c6 100644 --- a/src/MathProg/vcids.jl +++ b/src/MathProg/vcids.jl @@ -49,12 +49,25 @@ function Id{VC}( end Base.hash(a::Id, h::UInt) = hash(a.uid, h) -Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), -1, -1, -1, -1) # semaphore in the PMA (DynamicSparseArrays). -Base.isequal(a::Id{VC}, b::Id{VC}) where {VC} = Base.isequal(a.uid, b.uid) +Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), zero(Int32), -1, -1, -1) +Base.zero(::Id{VC}) where {VC} = Id{VC}(Duty{VC}(0), zero(Int32), -1, -1, -1) +Base.one(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), one(Int32), -1, -1, -1) +Base.typemax(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), typemax(Int32), -1, -1, -1) +Base.isequal(a::Id{VC}, b::Id{VC}) where {VC} = isequal(a.uid, b.uid) Base.promote_rule(::Type{T}, ::Type{<:Id}) where {T<:Integer} = T Base.promote_rule(::Type{<:Id}, ::Type{T}) where {T<:Integer} = T -Base.promote_rule(::Type{I}, ::Type{I}) where {I<:Id} = Int32 +Base.promote_rule(::Type{<:Id}, ::Type{<:Id}) = Int32 + +# Promotion mechanism will never call the following rule: +# Base.promote_rule(::Type{I}, ::Type{I}) where {I<:Id} = Int32 +# +# The problem is that an Id is an integer with additional information and we +# cannot generate additional information of a new id from the operation of two +# existing ids. +# As we want that all operations on ids results on operations on the uid, +# we redefine the promotion mechanism for Ids so that operations on Ids return integer: +Base.promote_type(::Type{I}, ::Type{I}) where {I<:Id} = Int32 Base.convert(::Type{Int}, id::I) where {I<:Id} = Int(id.uid) Base.convert(::Type{Int32}, id::I) where {I<:Id} = id.uid diff --git a/test/ColunaTests.jl b/test/ColunaTests.jl index 6b3f494de..e91029182 100644 --- a/test/ColunaTests.jl +++ b/test/ColunaTests.jl @@ -1,6 +1,6 @@ module ColunaTests using Base.CoreLogging: error - using DynamicSparseArrays, Coluna, TOML + using DynamicSparseArrays, SparseArrays, Coluna, TOML using ReTest, GLPK, ColunaDemos, JuMP, BlockDecomposition, Random, MathOptInterface, MathOptInterface.Utilities, Base.CoreLogging, Logging global_logger(ConsoleLogger(stderr, LogLevel(0))) diff --git a/test/Project.toml b/test/Project.toml index 5513cb026..6c8308dbd 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -12,4 +12,5 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Knapsacks = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" DynamicSparseArrays = "8086fd22-9a0c-46a5-a6c8-6e24676501fe" -ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89" \ No newline at end of file +ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 5820ad689..b54c0c361 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,5 +5,6 @@ include("ColunaTests.jl") retest(Coluna, ColunaTests) # Run a specific test: -# retest(ColunaTests, "strong branching") +# retest(ColunaTests, "MathProg - variable and constraint ids") + diff --git a/test/unit/Algorithm/colgen.jl b/test/unit/Algorithm/colgen.jl index 447b740b5..ba61b3fc7 100644 --- a/test/unit/Algorithm/colgen.jl +++ b/test/unit/Algorithm/colgen.jl @@ -120,4 +120,8 @@ end nb_new_cols = ClA.insert_columns!(master, sp_optstate, redcosts_spsols, algo, phase) @test nb_new_cols == 2 end -end \ No newline at end of file + + @testset "Stabilization" begin + + end +end diff --git a/test/unit/ColunaBase/hashtable.jl b/test/unit/ColunaBase/hashtable.jl index 91a295af8..2cd842bbd 100644 --- a/test/unit/ColunaBase/hashtable.jl +++ b/test/unit/ColunaBase/hashtable.jl @@ -1,24 +1,60 @@ -@testset "ColunaBase - hash table" begin +@testset "ColunaBase - hash table 1" begin + x = 'A' + y = 'B' + z = 'C' + + col1 = 1 + sol1 = [(x, 1.0), (z, 2.0)] + + col2 = 2 + sol2 = [(x, 2.0), (y, -1.0)] + + col3 = 3 + sol3 = [(y, -1.0), (z, 3.0)] + + col4 = 4 + sol4 = [(x, 1.0)] + + col5 = 5 + sol5 = [(z, -2.0), (y, 1.0)] + + ht = ClB.HashTable{Char,Int}() + + ClB.savesolid!(ht, col1, sol1) + ClB.savesolid!(ht, col2, sol2) + ClB.savesolid!(ht, col3, sol3) + ClB.savesolid!(ht, col4, sol4) + ClB.savesolid!(ht, col5, sol5) + + @test ClB.getsolids(ht, sol1) == [col1] + @test ClB.getsolids(ht, sol2) == [col2] + @test ClB.getsolids(ht, sol3) == [col3, col5] + @test ClB.getsolids(ht, sol4) == [col4] + @test ClB.getsolids(ht, sol5) == [col3, col5] +end + +# Same test as "hash table 1" but we use VarIds from Coluna. +@testset "ColunaBase - hash table 2" begin x = ClMP.VarId(ClMP.OriginalVar, 1, 1) y = ClMP.VarId(ClMP.OriginalVar, 2, 1) z = ClMP.VarId(ClMP.OriginalVar, 3, 1) - col1 = ClMP.VarId(ClMP.MasterCol, 1, 2) + col1 = ClMP.VarId(ClMP.MasterCol, 4, 2) sol1 = [(x, 1.0), (z, 2.0)] - col2 = ClMP.VarId(ClMP.MasterCol, 2, 2) + col2 = ClMP.VarId(ClMP.MasterCol, 5, 2) sol2 = [(x, 2.0), (y, -1.0)] - col3 = ClMP.VarId(ClMP.MasterCol, 3, 2) + col3 = ClMP.VarId(ClMP.MasterCol, 6, 2) sol3 = [(y, -1.0), (z, 3.0)] - col4 = ClMP.VarId(ClMP.MasterCol, 4, 2) + col4 = ClMP.VarId(ClMP.MasterCol, 7, 2) sol4 = [(x, 1.0)] - col5 = ClMP.VarId(ClMP.MasterCol, 5, 2) + col5 = ClMP.VarId(ClMP.MasterCol, 8, 2) sol5 = [(z, -2.0), (y, 1.0)] - ht = ClB.HashTable{ClMP.VarId}() + ht = ClB.HashTable{ClMP.VarId, ClMP.VarId}() ClB.savesolid!(ht, col1, sol1) ClB.savesolid!(ht, col2, sol2) diff --git a/test/unit/ColunaBase/solsandbounds.jl b/test/unit/ColunaBase/solsandbounds.jl index 5843b3612..43e78526f 100644 --- a/test/unit/ColunaBase/solsandbounds.jl +++ b/test/unit/ColunaBase/solsandbounds.jl @@ -307,10 +307,8 @@ end dict_sol = Dict(1 => 2.0, 2 => 3.0, 3 => 4.0) primal_sol = Solution(model, collect(keys(dict_sol)), collect(values(dict_sol)), 0.0, ClB.FEASIBLE_SOL) - @test iterate(primal_sol) == iterate(primal_sol.sol) - _, state = iterate(primal_sol) - @test iterate(primal_sol, state) == iterate(primal_sol.sol, state) - @test length(primal_sol) == 3 + @test length(primal_sol) == typemax(Int) + @test nnz(primal_sol) == 3 @test primal_sol[1] == 2.0 primal_sol[1] = 5.0 # change the value @test primal_sol[1] == 5.0 diff --git a/test/unit/MathProg/formulations.jl b/test/unit/MathProg/formulations.jl index f2112c3f0..b39cae403 100644 --- a/test/unit/MathProg/formulations.jl +++ b/test/unit/MathProg/formulations.jl @@ -1,24 +1,26 @@ vid(uid) = ClMP.VarId(ClMP.OriginalVar, uid, 1) +struct DummyFormulation <: ClMP.AbstractFormulation end + @testset "MathProg - formulation" begin @testset "Dantzig-wolfe solution pool" begin pool_sols = dynamicsparse(ClMP.VarId, ClMP.VarId, Float64; fill_mode = false) - pool_ht = ClB.HashTable{ClMP.VarId}() - + pool_ht = ClB.HashTable{ClMP.VarId,ClMP.VarId}() + form = DummyFormulation() + sol1_id = vid(1) sol1_ids = [vid(4), vid(5), vid(8)] sol1_vals = [1.0, 2.0, 5.0] - sol1_repr = dynamicsparsevec(sol1_ids, sol1_vals) + sol1_repr = ClMP.PrimalSolution(form, sol1_ids, sol1_vals, 2.0, ClMP.FEASIBLE_SOL) sol2_id = vid(2) sol2_ids = [vid(4), vid(7), vid(9)] sol2_vals = [2.0, 2.0, 3.0] - sol2_repr = dynamicsparsevec(sol2_ids, sol2_vals) + sol2_repr = ClMP.PrimalSolution(form, sol2_ids, sol2_vals, 4.0, ClMP.FEASIBLE_SOL) sol3_ids = [vid(4), vid(7), vid(9)] sol3_vals = [1.0, 2.0, 3.0] - sol3_repr = dynamicsparsevec(sol3_ids, sol3_vals) - + sol3_repr = ClMP.PrimalSolution(form, sol3_ids, sol3_vals, 5.0, ClMP.FEASIBLE_SOL) addrow!(pool_sols, sol1_id, sol1_ids, sol1_vals) ClB.savesolid!(pool_ht, sol1_id, sol1_repr) diff --git a/test/unit/MathProg/manager.jl b/test/unit/MathProg/manager.jl index 16470906c..deeb9f274 100644 --- a/test/unit/MathProg/manager.jl +++ b/test/unit/MathProg/manager.jl @@ -1,4 +1,6 @@ DynamicSparseArrays.semaphore_key(::Type{Char}) = ' ' +Base.zero(::Type{Char}) = ' ' +DynamicSparseArrays.semaphore_key(::Type{Int}) = 0 function coefmatrix_factory() rows = ['a', 'a', 'b', 'b', 'd', 'f'] diff --git a/test/unit/MathProg/vcids.jl b/test/unit/MathProg/vcids.jl index 0e90d878b..5125b1b20 100644 --- a/test/unit/MathProg/vcids.jl +++ b/test/unit/MathProg/vcids.jl @@ -50,5 +50,9 @@ @test vid2 >= vid3 @test vid2 == vid3 @test isequal(vid2, vid3) + + @test vid1 + vid2 == 3 + @test vid1 - vid2 == -1 + @test vid1 * vid2 == 2 end end \ No newline at end of file