From 58b9fd08936151cff25fbf325333d6d1b204ee7f Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 14 Aug 2021 18:33:46 +0200 Subject: [PATCH 01/19] wip, improve support of singlvar constraints --- src/MOIwrapper.jl | 305 ++++++++++++++++----------- src/MathProg/MathProg.jl | 4 +- src/MathProg/constraint.jl | 19 ++ src/MathProg/formulation.jl | 80 ++++++- src/MathProg/manager.jl | 16 +- src/MathProg/reformulation.jl | 14 ++ src/MathProg/varconstr.jl | 17 +- src/optimize.jl | 2 + test/MathOptInterface/MOI_wrapper.jl | 12 +- test/bla | 27 +++ 10 files changed, 353 insertions(+), 143 deletions(-) create mode 100644 test/bla diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index f694da4b5..7c9537363 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -14,6 +14,31 @@ const SupportedConstrSets = Union{ } @enum(ObjectiveType, SINGLE_VARIABLE, SCALAR_AFFINE) + +# Helper for SingleVariable constraints +struct BoundConstraints + lower::Union{Nothing,SingleVarConstraint} + upper::Union{Nothing,SingleVarConstraint} + eq::Union{Nothing,SingleVarConstraint} +end + +setname!(bc, set_type, name) = nothing +setname!(bc, ::Type{<:MOI.ZeroOne}, name) = bc.lower.name = bc.upper.name = name +setname!(bc, ::Type{<:MOI.GreaterThan}, name) = bc.lower.name = name +setname!(bc, ::Type{<:MOI.LessThan}, name) = bc.upper.name = name +setname!(bc, ::Type{<:MOI.EqualTo}, name) = bc.eq.name = name +setname!(bc, ::Type{<:MOI.Interval}, name) = bc.lower.name = bc.upper.name = name + +setrhs!(bc, s::MOI.GreaterThan) = bc.lower.perendata.rhs = bc.lower.curdata.rhs = s.lower +setrhs!(bc, s::MOI.LessThan) = bc.upper.perendata.rhs = bc.upper.curdata.rhs = s.upper +setrhs!(bc, s::MOI.EqualTo) = bc.eq.perendata.rhs = bc.eq.curdata.rhs = s.value + +function setrhs!(bc, s::MOI.Interval) + bc.lower.perendata.rhs = bc.lower.curdata.rhs = s.lower + bc.upper.perendata.rhs = bc.upper.curdata.rhs = s.upper + return +end + mutable struct Optimizer <: MOI.AbstractOptimizer env::Env inner::Problem @@ -25,8 +50,9 @@ mutable struct Optimizer <: MOI.AbstractOptimizer moi_varids::Dict{VarId, MOI.VariableIndex} names_to_vars::Dict{String, MOI.VariableIndex} constrs::Dict{MOI.ConstraintIndex, Constraint} - constrs_on_single_var_to_vars::Dict{MOI.ConstraintIndex, VarId} - constrs_on_single_var_to_names::Dict{MOI.ConstraintIndex, String} + constrs_on_single_var::Dict{MOI.ConstraintIndex, BoundConstraints} + # constrs_on_single_var_to_vars::Dict{MOI.ConstraintIndex, VarId} + # constrs_on_single_var_to_names::Dict{MOI.ConstraintIndex, String} names_to_constrs::Dict{String, MOI.ConstraintIndex} result::OptimizationState disagg_result::Union{Nothing, OptimizationState} @@ -43,9 +69,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer #model.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() # TODO : check if necessary to have two dicts for variables model.moi_varids = Dict{VarId, MOI.VariableIndex}() model.names_to_vars = Dict{String, MOI.VariableIndex}() - model.constrs = Dict{MOI.ConstraintIndex, Union{Constraint, Nothing}}() - model.constrs_on_single_var_to_vars = Dict{MOI.ConstraintIndex, VarId}() - model.constrs_on_single_var_to_names = Dict{MOI.ConstraintIndex, String}() + model.constrs = Dict{MOI.ConstraintIndex, Constraint}() + model.constrs_on_single_var = Dict{MOI.ConstraintIndex, BoundConstraints}() + # model.constrs_on_single_var_to_vars = Dict{MOI.ConstraintIndex, VarId}() + # model.constrs_on_single_var_to_names = Dict{MOI.ConstraintIndex, String}() model.names_to_constrs = Dict{String, MOI.ConstraintIndex}() model.result = OptimizationState(get_optimization_target(model.inner)) model.disagg_result = nothing @@ -55,7 +82,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer end end -MOI.Utilities.supports_default_copy_to(::Coluna.Optimizer, ::Bool) = true +MOI.Utilities.supports_default_copy_to(::Optimizer, ::Bool) = true MOI.supports(::Optimizer, ::MOI.VariableName, ::Type{MOI.VariableIndex}) = true MOI.supports(::Optimizer, ::MOI.ConstraintName, ::Type{<:MOI.ConstraintIndex}) = true MOI.supports_constraint(::Optimizer, ::Type{<:SupportedConstrFunc}, ::Type{<:SupportedConstrSets}) = true @@ -96,7 +123,7 @@ function _get_orig_varid_in_form( return getid(getvar(form, origid)) end -MOI.get(optimizer::Coluna.Optimizer, ::MOI.SolverName) = "Coluna" +MOI.get(optimizer::Optimizer, ::MOI.SolverName) = "Coluna" function MOI.optimize!(optimizer::Optimizer) optimizer.result, optimizer.disagg_result = optimize!( @@ -105,14 +132,14 @@ function MOI.optimize!(optimizer::Optimizer) return end -function MOI.copy_to(dest::Coluna.Optimizer, src::MOI.ModelLike; kwargs...) +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kwargs...) return MOI.Utilities.automatic_copy_to(dest, src; kwargs...) end ############################################################################################ # Add variables ############################################################################################ -function MOI.add_variable(model::Coluna.Optimizer) +function MOI.add_variable(model::Optimizer) orig_form = get_original_formulation(model.inner) var = setvar!(orig_form, "v", OriginalVar) index = CleverDicts.add_item(model.vars, var) @@ -125,70 +152,83 @@ end ############################################################################################ # Add constraint ############################################################################################ -function _constraint_on_variable!(var::Variable, ::MOI.Integer) - # set perene data - var.perendata.kind = Integ - var.curdata.kind = Integ +function _constraint_on_variable!( + optimizer, form::Formulation, constrid, var::Variable, ::MOI.Integer +) + setperenkind!(form, var, Integ) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(nothing, nothing, nothing) return end -function _constraint_on_variable!(var::Variable, ::MOI.ZeroOne) - # set perene data - var.perendata.kind = Binary - var.curdata.kind = Binary - var.perendata.lb = max(0.0, var.perendata.lb) - var.curdata.lb = max(0.0, var.curdata.lb) - var.perendata.ub = min(1.0, var.perendata.ub) - var.curdata.ub = min(1.0, var.curdata.ub) +function _constraint_on_variable!( + optimizer, form::Formulation, constrid, var::Variable, ::MOI.ZeroOne +) + setperenkind!(form, var, Binary) + constr1 = setsinglevarconstr!( + form, "lb", getid(var), OriginalConstr; sense = Greater, rhs = 0.0 + ) + constr2 = setsinglevarconstr!( + form, "ub", getid(var), OriginalConstr; sense = Less, rhs = 1.0 + ) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(constr1, constr2, nothing) return end -function _constraint_on_variable!(var::Variable, set::MOI.GreaterThan{Float64}) - # set perene data - var.perendata.lb = max(set.lower, var.perendata.lb) - var.curdata.lb = max(set.lower, var.perendata.lb) +function _constraint_on_variable!( + optimizer, form::Formulation, constrid, var::Variable, set::MOI.GreaterThan{Float64} +) + constr = setsinglevarconstr!( + form, "lb", getid(var), OriginalConstr; sense = Greater, rhs = set.lower + ) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(constr, nothing, nothing) return end -function _constraint_on_variable!(var::Variable, set::MOI.LessThan{Float64}) - # set perene data - var.perendata.ub = min(set.upper, var.perendata.ub) - var.curdata.ub = min(set.upper, var.curdata.ub) +function _constraint_on_variable!( + optimizer, form::Formulation, constrid, var::Variable, set::MOI.LessThan{Float64} +) + constr = setsinglevarconstr!( + form, "ub", getid(var), OriginalConstr; sense = Less, rhs = set.upper + ) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(nothing, constr, nothing) return end -function _constraint_on_variable!(var::Variable, set::MOI.EqualTo{Float64}) - # set perene data - var.perendata.lb = max(set.value, var.perendata.lb) - var.curdata.lb = max(set.value, var.curdata.lb) - var.perendata.ub = min(set.value, var.perendata.ub) - var.curdata.ub = min(set.value, var.curdata.ub) +function _constraint_on_variable!( + optimizer, form::Formulation, constrid, var::Variable, set::MOI.EqualTo{Float64} +) + constr = setsinglevarconstr!( + form, "eq", getid(var), OriginalConstr; sense = Equal, rhs = set.value + ) + optimizer.constrs_on_single_var[constrid] = BoundConstraint(nothing, nothing, constr) return end -function _constraint_on_variable!(var::Variable, set::MOI.Interval{Float64}) - # set perene data - var.perendata.lb = max(set.lower, var.perendata.lb) - var.curdata.lb = max(set.lower, var.curdata.lb) - var.perendata.ub = min(set.upper, var.perendata.ub) - var.curdata.ub = min(set.upper, var.curdata.ub) +function _constraint_on_variable!( + optimizer, form::Formulation, constrid, var::Variable, set::MOI.Interval{Float64} +) + constr1 = setsinglevarconstr!( + form, "lb", getid(var), OriginalConstr; sense = Greater, rhs = set.lower + ) + constr2 = setsinglevarconstr!( + form, "ub", getid(var), OriginalConstr; sense = Less, rhs = set.upper + ) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(constr1, constr2, nothing) return end function MOI.add_constraint( - model::Coluna.Optimizer, func::MOI.SingleVariable, set::S + model::Optimizer, func::MOI.SingleVariable, set::S ) where {S<:SupportedVarSets} - orig_form = get_original_formulation(model.inner) + origform = get_original_formulation(model.inner) var = model.vars[func.variable] - _constraint_on_variable!(var, set) constrid = MOI.ConstraintIndex{MOI.SingleVariable, S}(func.variable.value) - model.constrs_on_single_var_to_names[constrid] = "" - model.constrs_on_single_var_to_vars[constrid] = getid(var) + _constraint_on_variable!(model, origform, constrid, var, set) return constrid end function MOI.add_constraint( - model::Coluna.Optimizer, func::MOI.ScalarAffineFunction{Float64}, set::S + model::Optimizer, func::MOI.ScalarAffineFunction{Float64}, set::S ) where {S<:SupportedConstrSets} orig_form = get_original_formulation(model.inner) members = Dict{VarId, Float64}() @@ -212,18 +252,21 @@ end ############################################################################################ # Delete and modify variable ############################################################################################ -function MOI.delete(model::Coluna.Optimizer, vi::MOI.VariableIndex) +function MOI.delete(model::Optimizer, vi::MOI.VariableIndex) + MOI.throw_if_not_valid(model, vi) MOI.modify(model, MoiObjective(), MOI.ScalarCoefficientChange(vi, 0.0)) - for (ci, _) in model.constrs_on_single_var_to_vars - if ci.value == vi.value - MOI.delete(model, ci) - break - end - end for (ci, _) in model.constrs MOI.modify(model, ci, MOI.ScalarCoefficientChange(vi, 0.0)) end varid = getid(model.vars[vi]) + for (ci, constrs) in model.constrs_on_single_var + for constr in [constrs.lower, constrs.upper, constrs.eq] + if constr !== nothing && constr.varid == varid + MOI.delete(model, ci) + break + end + end + end delete!(get_original_formulation(model.inner), varid) delete!(model.moi_varids, varid) delete!(model.vars, vi) @@ -232,7 +275,7 @@ function MOI.delete(model::Coluna.Optimizer, vi::MOI.VariableIndex) end function MOI.modify( - model::Coluna.Optimizer, ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, + model::Optimizer, ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, change::MathOptInterface.ScalarCoefficientChange{Float64} ) setperencost!( @@ -244,16 +287,29 @@ end ############################################################################################ # Delete and modify constraint ############################################################################################ -# issue #583 -# function MOI.delete( -# model::Coluna.Optimizer, ci::MOI.ConstraintIndex{F,S} -# ) where {F<:MOI.SingleVariable,S} -# return -# end +function MOI.delete( + model::Optimizer, ci::MOI.ConstraintIndex{F,S} +) where {F<:MOI.SingleVariable,S} + MOI.throw_if_not_valid(model, ci) + origform = get_original_formulation(model.inner) + constrs = model.constrs_on_single_var[ci] + if constrs.lower !== nothing + delete!(origform.manager.var_bound_constrs, getid(constrs.lower)) + end + if constrs.upper !== nothing + delete!(origform.manager.var_bound_constrs, getid(constrs.upper)) + end + if constrs.eq !== nothing + delete!(origform.manager.var_bound_constrs, getid(constrs.eq)) + end + delete!(model.constrs_on_single_var, ci) + return +end function MOI.delete( - model::Coluna.Optimizer, ci::MOI.ConstraintIndex{F,S} + model::Optimizer, ci::MOI.ConstraintIndex{F,S} ) where {F<:MOI.ScalarAffineFunction{Float64},S} + MOI.throw_if_not_valid(model, ci) constrid = getid(model.constrs[ci]) orig_form = get_original_formulation(model.inner) coefmatrix = getcoefmatrix(orig_form) @@ -270,17 +326,19 @@ function MOI.delete( end function MOI.modify( - model::Coluna.Optimizer, ci::MOI.ConstraintIndex{F,S}, + model::Optimizer, ci::MOI.ConstraintIndex{F,S}, change::MOI.ScalarConstantChange{Float64} ) where {F<:MOI.ScalarAffineFunction{Float64},S} + MOI.throw_if_not_valid(model, ci) setperenrhs!(get_original_formulation(model.inner), model.constrs[ci], change.new_constant) return end function MOI.modify( - model::Coluna.Optimizer, ci::MOI.ConstraintIndex{F,S}, + model::Optimizer, ci::MOI.ConstraintIndex{F,S}, change::MOI.ScalarCoefficientChange{Float64} ) where {F<:MOI.ScalarAffineFunction{Float64},S} + MOI.throw_if_not_valid(model, ci) varid = getid(model.vars[change.variable]) constrid = getid(model.constrs[ci]) getcoefmatrix(get_original_formulation(model.inner))[constrid, varid] = change.new_coefficient @@ -290,11 +348,11 @@ end ############################################################################################ # Get variables ############################################################################################ -function MOI.get(model::Coluna.Optimizer, ::Type{MOI.VariableIndex}, name::String) +function MOI.get(model::Optimizer, ::Type{MOI.VariableIndex}, name::String) return get(model.names_to_vars, name, nothing) end -function MOI.get(model::Coluna.Optimizer, ::MOI.ListOfVariableIndices) +function MOI.get(model::Optimizer, ::MOI.ListOfVariableIndices) indices = Vector{MathOptInterface.VariableIndex}() for (_, value) in model.moi_varids push!(indices, value) @@ -314,14 +372,14 @@ function _moi_bounds_type(lb, ub) end function MOI.get( - model::Coluna.Optimizer, C::Type{MOI.ConstraintIndex{F,S}}, name::String + model::Optimizer, C::Type{MOI.ConstraintIndex{F,S}}, name::String ) where {F,S} index = get(model.names_to_constrs, name, nothing) typeof(index) == C && return index return nothing end -function MOI.get(model::Coluna.Optimizer, ::MOI.ListOfConstraints) +function MOI.get(model::Optimizer, ::MOI.ListOfConstraints) orig_form = get_original_formulation(model.inner) constraints = Set{Tuple{DataType, DataType}}() for (id, var) in model.vars @@ -354,7 +412,7 @@ function _add_constraint!( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ListOfConstraintIndices{F, S} + model::Optimizer, ::MOI.ListOfConstraintIndices{F, S} ) where {F<:MOI.ScalarAffineFunction{Float64}, S} indices = MOI.ConstraintIndex{F,S}[] for (id, constr) in model.constrs @@ -364,11 +422,11 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ListOfConstraintIndices{F, S} + model::Optimizer, ::MOI.ListOfConstraintIndices{F, S} ) where {F<:MOI.SingleVariable, S} indices = MOI.ConstraintIndex{F,S}[] - for (id, _) in model.constrs_on_single_var_to_vars - if S == typeof(MOI.get(model, MOI.ConstraintSet(), id)) + for (id, _) in model.constrs_on_single_var + if S === typeof(MOI.get(model, MOI.ConstraintSet(), id)) push!(indices, id) end end @@ -376,7 +434,7 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintFunction, index::MOI.ConstraintIndex{F,S} + model::Optimizer, ::MOI.ConstraintFunction, index::MOI.ConstraintIndex{F,S} ) where {F<:MOI.ScalarAffineFunction{Float64}, S} orig_form = get_original_formulation(model.inner) constrid = getid(model.constrs[index]) @@ -388,13 +446,13 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintFunction, index::MOI.ConstraintIndex{F,S} + model::Optimizer, ::MOI.ConstraintFunction, index::MOI.ConstraintIndex{F,S} ) where {F<:MOI.SingleVariable, S} return MOI.SingleVariable(MOI.VariableIndex(index.value)) end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{F,S} + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{F,S} ) where {F<:MOI.ScalarAffineFunction{Float64},S} orig_form = get_original_formulation(model.inner) rhs = getperenrhs(orig_form, model.constrs[index]) @@ -402,7 +460,7 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}} ) orig_form = get_original_formulation(model.inner) @@ -411,18 +469,20 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}} ) + MOI.throw_if_not_valid(model, index) orig_form = get_original_formulation(model.inner) ub = getperenub(orig_form, model.vars[MOI.VariableIndex(index.value)]) return MOI.LessThan(ub) end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{Float64}} ) + MOI.throw_if_not_valid(model, index) orig_form = get_original_formulation(model.inner) lb = getperenlb(orig_form, model.vars[MOI.VariableIndex(index.value)]) ub = getperenub(orig_form, model.vars[MOI.VariableIndex(index.value)]) @@ -431,9 +491,10 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{Float64}} ) + MOI.throw_if_not_valid(model, index) orig_form = get_original_formulation(model.inner) lb = getperenlb(orig_form, model.vars[MOI.VariableIndex(index.value)]) ub = getperenub(orig_form, model.vars[MOI.VariableIndex(index.value)]) @@ -441,20 +502,20 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.ZeroOne} ) return MOI.ZeroOne() end function MOI.get( - model::Coluna.Optimizer, ::MOI.ConstraintSet, + model::Optimizer, ::MOI.ConstraintSet, index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer} ) return MOI.Integer() end -function MOI.get(model::Coluna.Optimizer, ::Type{MOI.ConstraintIndex}, name::String) +function MOI.get(model::Optimizer, ::Type{MOI.ConstraintIndex}, name::String) return get(model.names_to_constrs, name, nothing) end @@ -462,7 +523,7 @@ end # Attributes of variables ############################################################################################ function MOI.set( - model::Coluna.Optimizer, ::BD.VariableDecomposition, varid::MOI.VariableIndex, + model::Optimizer, ::BD.VariableDecomposition, varid::MOI.VariableIndex, annotation::BD.Annotation ) store!(model.annotations, annotation, model.vars[varid]) @@ -470,8 +531,9 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::MOI.VariableName, varid::MOI.VariableIndex, name::String + model::Optimizer, ::MOI.VariableName, varid::MOI.VariableIndex, name::String ) + MOI.throw_if_not_valid(model, varid) var = model.vars[varid] # TODO : rm set perene name var.name = name @@ -480,19 +542,19 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::BD.VarBranchingPriority, varid::MOI.VariableIndex, branching_priority::Int + model::Optimizer, ::BD.VarBranchingPriority, varid::MOI.VariableIndex, branching_priority::Int ) var = model.vars[varid] var.branching_priority = Float64(branching_priority) return end -function MOI.get(model::Coluna.Optimizer, ::MOI.VariableName, index::MOI.VariableIndex) +function MOI.get(model::Optimizer, ::MOI.VariableName, index::MOI.VariableIndex) orig_form = get_original_formulation(model.inner) return getname(orig_form, model.vars[index]) end -function MOI.get(model::Coluna.Optimizer, ::BD.VarBranchingPriority, varid::MOI.VariableIndex) +function MOI.get(model::Optimizer, ::BD.VarBranchingPriority, varid::MOI.VariableIndex) var = model.vars[varid] return var.branching_priority end @@ -515,7 +577,7 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::BD.ConstraintDecomposition, constrid::MOI.ConstraintIndex, + model::Optimizer, ::BD.ConstraintDecomposition, constrid::MOI.ConstraintIndex, annotation::BD.Annotation ) constr = get(model.constrs, constrid, nothing) @@ -526,7 +588,7 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::MOI.ConstraintName, constrid::MOI.ConstraintIndex{F,S}, name::String + model::Optimizer, ::MOI.ConstraintName, constrid::MOI.ConstraintIndex{F,S}, name::String ) where {F<:MOI.ScalarAffineFunction,S} MOI.throw_if_not_valid(model, constrid) constr = model.constrs[constrid] @@ -537,17 +599,17 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::MOI.ConstraintName, constrid::MOI.ConstraintIndex{F,S}, name::String + model::Optimizer, ::MOI.ConstraintName, constrid::MOI.ConstraintIndex{F,S}, name::String ) where {F<:MOI.SingleVariable,S} MOI.throw_if_not_valid(model, constrid) - model.constrs_on_single_var_to_names[constrid] = name + setname!(model.constrs_on_single_var[constrid], S, name) model.names_to_constrs[name] = constrid return end function MOI.set( model::Coluna.Optimizer, ::MOI.ConstraintSet, constrid::MOI.ConstraintIndex{F,S}, set::S -) where {F,S<:SupportedConstrSets} +) where {F<:SupportedConstrFunc,S<:SupportedConstrSets} MOI.throw_if_not_valid(model, constrid) origform = get_original_formulation(model.inner) constr = model.constrs[constrid] @@ -556,7 +618,16 @@ function MOI.set( return end -function MOI.get(model::Coluna.Optimizer, ::MOI.ConstraintName, constrid::MOI.ConstraintIndex) +function MOI.set( + model::Coluna.Optimizer, ::MOI.ConstraintSet, constrid::MOI.ConstraintIndex{F,S}, set::S +) where {F<:MOI.SingleVariable,S<:SupportedConstrSets} + MOI.throw_if_not_valid(model, constrid) + constrs = model.constrs_on_single_var[constrid] + setrhs!(constrs, set) + return +end + +function MOI.get(model::Optimizer, ::MOI.ConstraintName, constrid::MOI.ConstraintIndex) MOI.throw_if_not_valid(model, constrid) orig_form = get_original_formulation(model.inner) constr = get(model.constrs, constrid, nothing) @@ -573,7 +644,7 @@ end ############################################################################################ # Objective ############################################################################################ -function MOI.set(model::Coluna.Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) +function MOI.set(model::Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) orig_form = get_original_formulation(model.inner) if sense == MOI.MIN_SENSE model.feasibility_sense = false @@ -588,14 +659,14 @@ function MOI.set(model::Coluna.Optimizer, ::MOI.ObjectiveSense, sense::MOI.Optim return end -function MOI.get(model::Coluna.Optimizer, ::MOI.ObjectiveSense) +function MOI.get(model::Optimizer, ::MOI.ObjectiveSense) sense = getobjsense(get_original_formulation(model.inner)) model.feasibility_sense && return MOI.FEASIBILITY_SENSE sense == MaxSense && return MOI.MAX_SENSE return MOI.MIN_SENSE end -function MOI.get(model::Coluna.Optimizer, ::MOI.ObjectiveFunctionType) +function MOI.get(model::Optimizer, ::MOI.ObjectiveFunctionType) if model.objective_type == SINGLE_VARIABLE return MOI.SingleVariable end @@ -604,7 +675,7 @@ function MOI.get(model::Coluna.Optimizer, ::MOI.ObjectiveFunctionType) end function MOI.set( - model::Coluna.Optimizer, ::MOI.ObjectiveFunction{F}, func::F + model::Optimizer, ::MOI.ObjectiveFunction{F}, func::F ) where {F<:MOI.ScalarAffineFunction{Float64}} model.objective_type = SCALAR_AFFINE origform = get_original_formulation(model.inner) @@ -626,19 +697,16 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::MOI.ObjectiveFunction{MOI.SingleVariable}, + model::Optimizer, ::MOI.ObjectiveFunction{MOI.SingleVariable}, func::MOI.SingleVariable ) model.objective_type = SINGLE_VARIABLE - var = model.vars[func.variable] - # TODO : rm set perene cost - var.perendata.cost = 1.0 - var.curdata.cost = 1.0 + setperencost!(get_original_formulation(model.inner), model.vars[func.variable], 1.0) return end function MOI.get( - model::Coluna.Optimizer, ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}} + model::Optimizer, ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}} ) @assert model.objective_type == SCALAR_AFFINE orig_form = get_original_formulation(model.inner) @@ -653,7 +721,7 @@ function MOI.get( end function MOI.get( - model::Coluna.Optimizer, ::MOI.ObjectiveFunction{MOI.SingleVariable} + model::Optimizer, ::MOI.ObjectiveFunction{MOI.SingleVariable} ) @assert model.objective_type == SINGLE_VARIABLE orig_form = get_original_formulation(model.inner) @@ -669,29 +737,29 @@ end ############################################################################################ # Attributes of model ############################################################################################ -function MOI.set(model::Coluna.Optimizer, ::BD.DecompositionTree, tree::BD.Tree) +function MOI.set(model::Optimizer, ::BD.DecompositionTree, tree::BD.Tree) model.annotations.tree = tree return end -function MOI.set(model::Coluna.Optimizer, ::BD.ObjectiveDualBound, db) +function MOI.set(model::Optimizer, ::BD.ObjectiveDualBound, db) set_initial_dual_bound!(model.inner, db) return end -function MOI.set(model::Coluna.Optimizer, ::BD.ObjectivePrimalBound, pb) +function MOI.set(model::Optimizer, ::BD.ObjectivePrimalBound, pb) set_initial_primal_bound!(model.inner, pb) return end -function _customdata!(model::Coluna.Optimizer, type::DataType) +function _customdata!(model::Optimizer, type::DataType) haskey(model.env.custom_families_id, type) && return model.env.custom_families_id[type] = length(model.env.custom_families_id) return end function MOI.set( - model::Coluna.Optimizer, ::BD.CustomVars, customvars::Vector{DataType} + model::Optimizer, ::BD.CustomVars, customvars::Vector{DataType} ) for customvar in customvars _customdata!(model, customvar) @@ -700,7 +768,7 @@ function MOI.set( end function MOI.set( - model::Coluna.Optimizer, ::BD.CustomConstrs, customconstrs::Vector{DataType} + model::Optimizer, ::BD.CustomConstrs, customconstrs::Vector{DataType} ) for customconstr in customconstrs _customdata!(model, customconstr) @@ -708,15 +776,16 @@ function MOI.set( return end -function MOI.empty!(model::Coluna.Optimizer) +function MOI.empty!(model::Optimizer) model.inner = Problem(model.env) model.annotations = Annotations() model.vars = CleverDicts.CleverDict{MOI.VariableIndex, Variable}() model.env.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() model.moi_varids = Dict{VarId, MOI.VariableIndex}() model.constrs = Dict{MOI.ConstraintIndex, Constraint}() - model.constrs_on_single_var_to_vars = Dict{MOI.ConstraintIndex, VarId}() - model.constrs_on_single_var_to_names = Dict{MOI.ConstraintIndex, String}() + model.constrs_on_single_var = Dict{MOI.ConstraintIndex, BoundConstraints}() + #model.constrs_on_single_var_to_vars = Dict{MOI.ConstraintIndex, VarId}() + #model.constrs_on_single_var_to_names = Dict{MOI.ConstraintIndex, String}() if model.default_optimizer_builder !== nothing set_default_optimizer_builder!(model.inner, model.default_optimizer_builder) end @@ -726,12 +795,12 @@ function MOI.empty!(model::Coluna.Optimizer) end mutable struct ColumnInfo <: BD.AbstractColumnInfo - optimizer::Coluna.Optimizer + optimizer::Optimizer column_var_id::VarId column_val::Float64 end -function BD.getsolutions(model::Coluna.Optimizer, k) +function BD.getsolutions(model::Optimizer, k) ip_primal_sol = get_best_ip_primal_sol(model.disagg_result) sp_columns_info = Vector{ColumnInfo}() for (varid, val) in ip_primal_sol @@ -753,7 +822,7 @@ function BD.value(info::ColumnInfo, index::MOI.VariableIndex) return info.column_val * getprimalsolmatrix(spform)[varid, info.column_var_id] end -function MOI.get(model::Coluna.Optimizer, ::MOI.NumberOfVariables) +function MOI.get(model::Optimizer, ::MOI.NumberOfVariables) orig_form = get_original_formulation(model.inner) return length(getvars(orig_form)) end @@ -782,7 +851,7 @@ end function MOI.is_valid( optimizer::Optimizer, index::MOI.ConstraintIndex{F,S} ) where {F<:MOI.SingleVariable,S} - return haskey(optimizer.constrs_on_single_var_to_names, index) + return haskey(optimizer.constrs_on_single_var, index) end function MOI.is_valid( @@ -856,7 +925,7 @@ end function MOI.get( optimizer::Optimizer, ::MOI.ConstraintPrimal, index::MOI.ConstraintIndex{F,S} ) where {F<:MOI.SingleVariable,S} - varid = get(optimizer.constrs_on_single_var_to_vars, index, nothing) + varid = get(optimizer.constrs_on_single_var, index, nothing) if varid === nothing @warn "Could not find constraint with id $(index)." return NaN diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index fe17cd8d1..a8238534c 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -92,12 +92,12 @@ export Original, DwMaster, BendersMaster, DwSp, BendersSp export isanArtificialDuty, isaStaticDuty, isaDynamicDuty, isanOriginalRepresentatives # Types and methods related to variables and constraints -export Variable, Constraint, VarId, ConstrId, VarMembership, ConstrMembership, +export Variable, Constraint, VarId, ConstrId, VarMembership, ConstrMembership, SingleVarConstraint, getperencost, setperencost!, getcurcost, setcurcost!, getperenlb, getcurlb, setcurlb!, getperenub, getcurub, setcurub!, getperenrhs, setperenrhs!, getcurrhs, setcurrhs!, getperensense, setperensense!, getcursense, setcursense!, getperenkind, getcurkind, setcurkind!, getperenincval, getcurincval, setcurincval!, isperenactive, iscuractive, activate!, deactivate!, - isexplicit, getname, getbranchingpriority, reset!, getreducedcost + isexplicit, getname, getbranchingpriority, reset!, getreducedcost, setperenkind!, setsinglevarconstr! # Types & methods related to solutions & bounds export PrimalBound, DualBound, PrimalSolution, DualSolution, ObjValues diff --git a/src/MathProg/constraint.jl b/src/MathProg/constraint.jl index a21b78e61..277a89371 100644 --- a/src/MathProg/constraint.jl +++ b/src/MathProg/constraint.jl @@ -69,6 +69,25 @@ function Constraint( ) end +mutable struct SingleVarConstraint <: AbstractVarConstr + id::Id{Constraint} + name::String + varid::VarId + perendata::ConstrData + curdata::ConstrData + moirecord::MoiConstrRecord +end + +function SingleVarConstraint( + id::ConstrId, varid::VarId, name::String; + constr_data = ConstrData(), moi_index::MoiConstrIndex = MoiConstrIndex(), +) + return SingleVarConstraint( + id, name, varid, constr_data, ConstrData(constr_data), + MoiConstrRecord(index = moi_index) + ) +end + mutable struct RobustConstraintsGenerator nb_generated::Int kind::ConstrKind diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index ceae372f6..25a09f4c5 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -144,7 +144,7 @@ set_matrix_coeff!( setvar!( formulation, name, duty; cost = 0.0, - lb = 0.0, + lb = -Inf, ub = Inf, kind = Continuous, is_active = true, @@ -168,7 +168,7 @@ function setvar!( name::String, duty::Duty{Variable}; cost::Float64 = 0.0, - lb::Float64 = 0.0, + lb::Float64 = -Inf, ub::Float64 = Inf, kind::VarKind = Continuous, inc_val::Float64 = 0.0, @@ -461,6 +461,33 @@ function setconstr!( return constr end +""" +TODO + +This constraint is never explicit because it's used to compute bounds stored in the variable +""" +function setsinglevarconstr!( + form::Formulation, + name::String, + varid::VarId, + duty::Duty{Constraint}; + rhs::Float64 = 0.0, + kind::ConstrKind = Essential, + sense::ConstrSense = Greater, + inc_val::Float64 = 0.0, + is_active::Bool = true, + moi_index::MoiConstrIndex = MoiConstrIndex(), + id = generateconstrid(duty, form) +) + if getduty(id) != duty + id = ConstrId(duty, id, -1) + end + c_data = ConstrData(rhs, kind, sense, inc_val, is_active, true) + constr = SingleVarConstraint(id, varid, name; constr_data = c_data, moi_index = moi_index) + form.manager.var_bound_constrs[id] = constr + return constr +end + function set_robust_constr_generator!(form::Formulation, kind::ConstrKind, alg::Function) constrgen = RobustConstraintsGenerator(0, kind, alg) push!(form.manager.robust_constr_generators, constrgen) @@ -667,6 +694,55 @@ function push_optimizer!(form::Formulation, builder::Function) return end +############################################################################################ +# Bounds propagation +############################################################################################ +# if a new single var constraint has been added, we should call this method + +function _update_var_cur_lb!(form::Formulation, var::Variable, lb) + cur_lb = getcurlb(form, var) + if cur_lb < lb + setcurlb!(form, var, lb) + end + return +end + +function _update_var_cur_ub!(form::Formulation, var::Variable, ub) + cur_ub = getcurub(form, var) + if cur_ub > ub + setcurub!(form, var, ub) + end + return +end + +function _update_bounds!(form::Formulation, var::Variable, ::Val{Greater}, rhs) + _update_var_cur_lb!(form, var, rhs) + return +end + +function _update_bounds!(form::Formulation, var::Variable, ::Val{Less}, rhs) + _update_var_cur_ub!(form, var, rhs) + return +end + +function _update_bounds!(form::Formulation, var::Variable, ::Val{Equal}, rhs) + _update_var_cur_lb!(form, var, rhs) + _update_var_cur_ub!(form, var, rhs) + return +end + +function bounds_propagation!(form::Formulation) + for (_, constr) in form.manager.var_bound_constrs + var = getvar(form, constr.varid) + _update_bounds!(form, var, Val(constr.curdata.sense), constr.curdata.rhs) + end + return +end + +############################################################################################ +# Methods to show a formulation +############################################################################################ + function _show_obj_fun(io::IO, form::Formulation) print(io, getobjsense(form), " ") vars = filter(v -> isexplicit(form, v.first), getvars(form)) diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index 740356465..eba6ed32f 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -1,7 +1,5 @@ const DynSparseVector{I} = DynamicSparseArrays.PackedMemoryArray{I, Float64} -const VarDict = Dict{VarId, Variable} -const ConstrDict = Dict{ConstrId, Constraint} const VarMembership = Dict{VarId, Float64} const ConstrMembership = Dict{ConstrId, Float64} const ConstrConstrMatrix = DynamicSparseArrays.DynamicSparseMatrix{ConstrId,ConstrId,Float64} @@ -12,11 +10,12 @@ const VarVarMatrix = DynamicSparseArrays.DynamicSparseMatrix{VarId,VarId,Float64 DynamicSparseArrays.semaphore_key(::Type{I}) where {I <: Id} = zero(I) mutable struct FormulationManager - vars::VarDict - constrs::ConstrDict + vars::Dict{VarId, Variable} + constrs::Dict{ConstrId, Constraint} + var_bound_constrs::Dict{ConstrId, SingleVarConstraint} # ids of the constraint of type : single variable >= bound objective_constant::Float64 coefficients::ConstrVarMatrix # rows = constraints, cols = variables - expressions::VarVarMatrix # cols = variables, rows = expressions + # expressions::VarVarMatrix # cols = variables, rows = expressions (not implemented yet) primal_sols::VarVarMatrix # cols = primal solutions with varid, rows = variables primal_sols_custom_data::Dict{VarId, BD.AbstractCustomData} primal_sol_costs::DynSparseVector{VarId} # primal solutions with varid map to their cost @@ -27,14 +26,15 @@ mutable struct FormulationManager end function FormulationManager(; custom_families_id = Dict{BD.AbstractCustomData,Int}()) - vars = VarDict() - constrs = ConstrDict() + vars = Dict{VarId, Variable}() + constrs = Dict{ConstrId, Constraint}() return FormulationManager( vars, constrs, + Dict{ConstrId, SingleVarConstraint}(), 0.0, dynamicsparse(ConstrId, VarId, Float64), - dynamicsparse(VarId, VarId, Float64; fill_mode = false), + #dynamicsparse(VarId, VarId, Float64; fill_mode = false), dynamicsparse(VarId, VarId, Float64; fill_mode = false), Dict{VarId,Any}(), dynamicsparsevec(VarId[], Float64[]), diff --git a/src/MathProg/reformulation.jl b/src/MathProg/reformulation.jl index 3f05969cd..962576b5e 100644 --- a/src/MathProg/reformulation.jl +++ b/src/MathProg/reformulation.jl @@ -100,6 +100,20 @@ subproblem with id `spid`. """ get_dw_pricing_sp_lb_constrid(r::Reformulation, spid::FormId) = r.dw_pricing_sp_lb[spid] +############################################################################################ +# Bounds propagation +############################################################################################ +function bounds_propagation!(reform::Reformulation) + bounds_propagation!(getmaster(reform)) + for (_, sp) in get_dw_pricing_sps(reform) + bounds_propagation!(sp) + end + for (_, sp) in get_benders_sep_sps(reform) + bounds_propagation!(sp) + end + return +end + # Following two functions are temporary, we must store a pointer to the vc # being represented by a representative vc function vc_belongs_to_formulation(form::Formulation, vc::AbstractVarConstr) diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index 8b16eacfe..2b6f75924 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -209,6 +209,19 @@ Return the current kind of a variable in a formulation. getcurkind(form::Formulation, varid::VarId) = getcurkind(form, getvar(form, varid)) getcurkind(form::Formulation, var::Variable) = var.curdata.kind +""" + setperenkind!(formulation, variable, kind) + setperenkind!(formulation, varid, kind) + +Set the perennial kind of a variable in a formulation. +This change is then propagated to the current kind of the variable. +""" +function setperenkind!(form::Formulation, var::Variable, kind::VarKind) + var.perendata.kind = kind + return setcurkind!(form, var, kind) +end +setperenkind!(form::Formulation, varid::VarId, kind::VarKind) = setperenkind!(form, getvar(form, varid), kind) + """ setcurkind!(formulation, variable, kind::VarKind) setcurkind!(formulation, varid, kind::VarKind) @@ -462,13 +475,13 @@ Delete a variable or a constraint from a formulation. """ function Base.delete!(form::Formulation, varid::VarId) delete!(form.manager.vars, varid) - delete!(form.buffer.var_buffer.added, varid) + remove!(form.buffer, varid) return end Base.delete!(form::Formulation, var::Variable) = delete!(form, getid(var)) function Base.delete!(form::Formulation, constrid::ConstrId) - delete!(form.buffer.constr_buffer.added, constrid) + remove!(form.buffer, constrid) delete!(form.manager.constrs, constrid) end Base.delete!(form::Formulation, constr::Constraint) = delete!(form, getid(constr)) diff --git a/src/optimize.jl b/src/optimize.jl index 33eeb0387..fbacaaad8 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -79,6 +79,7 @@ end function optimize!( reform::MathProg.Reformulation, env::Env, initial_primal_bound, initial_dual_bound ) + MathProg.bounds_propagation!(reform) master = getmaster(reform) initstate = OptimizationState( master, @@ -126,6 +127,7 @@ end function optimize!( form::MathProg.Formulation, env::Env, initial_primal_bound, initial_dual_bound ) + MathProg.bounds_propagation!(form) initstate = OptimizationState( form, ip_primal_bound = initial_primal_bound, diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 14c147ace..8411f7448 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -190,22 +190,12 @@ const CONSTRAINTDUAL_SINGLEVAR = [ "linear14" ] -const DELETE_SINGLEVAR_CONSTR = [ - # BUG: issue #583 - "linear5", - "linear14" -] - const UNCOVERED_TERMINATION_STATUS = [ "linear8b", # DUAL_INFEASIBLE or INFEASIBLE_OR_UNBOUNDED required "linear8c" # DUAL_INFEASIBLE or INFEASIBLE_OR_UNBOUNDED required ] const SET_CONSTRAINTSET = [ - # BUG - "linear4", - "linear6", - "linear7" ] @testset "Unit Basic/MIP" begin @@ -236,7 +226,7 @@ end @testset "Continuous Linear" begin MOIT.contlineartest(BRIDGED, CONFIG, vcat( - CONSTRAINTDUAL_SINGLEVAR, DELETE_SINGLEVAR_CONSTR, UNCOVERED_TERMINATION_STATUS, SET_CONSTRAINTSET, [ + CONSTRAINTDUAL_SINGLEVAR, UNCOVERED_TERMINATION_STATUS, [ "partial_start" # VariablePrimalStart not supported ] )) diff --git a/test/bla b/test/bla new file mode 100644 index 000000000..4038d4ea8 --- /dev/null +++ b/test/bla @@ -0,0 +1,27 @@ +* Problem: +* Class: LP +* Rows: 3 +* Columns: 4 +* Non-zeros: 6 +* Format: Free MPS +* +NAME +ROWS + N R0000000 + L cost[3] + L puresecondstage[2] + L tech2[2] +COLUMNS + C0000001 tech2[2] -1 + C0000002 tech2[2] 1 + C0000003 R0000000 -1 cost[3] 1 + y[2] tech2[2] 1 puresecondstage[2] 1 + y[2] cost[3] -1 +RHS + RHS1 cost[3] -1 puresecondstage[2] 1 + RHS1 tech2[2] 1 +BOUNDS + UP BND1 C0000001 1 + UP BND1 C0000002 1 + UP BND1 y[2] 1 +ENDATA From 28036863bf56bb322eccb4c9f2acf7d54ce07dda Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 14 Aug 2021 19:20:15 +0200 Subject: [PATCH 02/19] wip --- src/MOIwrapper.jl | 6 ++-- src/MathProg/formulation.jl | 53 ++++++++++++++++++++-------- src/MathProg/manager.jl | 19 ++++++++-- src/decomposition.jl | 5 ++- test/MathOptInterface/MOI_wrapper.jl | 1 - 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 7c9537363..58452df2b 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -294,13 +294,13 @@ function MOI.delete( origform = get_original_formulation(model.inner) constrs = model.constrs_on_single_var[ci] if constrs.lower !== nothing - delete!(origform.manager.var_bound_constrs, getid(constrs.lower)) + delete!(origform.manager.single_var_constrs, getid(constrs.lower)) end if constrs.upper !== nothing - delete!(origform.manager.var_bound_constrs, getid(constrs.upper)) + delete!(origform.manager.single_var_constrs, getid(constrs.upper)) end if constrs.eq !== nothing - delete!(origform.manager.var_bound_constrs, getid(constrs.eq)) + delete!(origform.manager.single_var_constrs, getid(constrs.eq)) end delete!(model.constrs_on_single_var, ci) return diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 25a09f4c5..108a75efe 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -21,7 +21,7 @@ A `Formulation` stores a mixed-integer linear program. obj_sense::Type{<:Coluna.AbstractSense} = MinSense ) -Create a new formulation in the Coluna's environment `env`. +Creates a new formulation in the Coluna's environment `env`. Arguments are `duty` that contains specific information related to the duty of the formulation, `parent_formulation` that is the parent formulation (master for a subproblem, reformulation for a master, `nothing` by default), and `obj_sense` the sense of the objective @@ -52,40 +52,59 @@ ColunaBase.getstorage(form::Formulation) = form.storage """ haskey(formulation, id) -> Bool -Return `true` if `formulation` has a variable or a constraint with given `id`. +Returns `true` if `formulation` has a variable or a constraint with given `id`. """ haskey(form::Formulation, id::VarId) = haskey(form.manager.vars, id) -haskey(form::Formulation, id::ConstrId) = haskey(form.manager.constrs, id) +haskey(form::Formulation, id::ConstrId) = haskey(form.manager.constrs, id) || haskey(form.manager.single_var_constrs, id) """ getvar(formulation, varid) -> Variable -Return the variable with given `varid` that belongs to `formulation`. +Returns the variable with given `varid` that belongs to `formulation`. """ getvar(form::Formulation, id::VarId) = get(form.manager.vars, id, nothing) """ getconstr(formulation, constrid) -> Constraint -Return the constraint with given `constrid` that belongs to `formulation`. +Returns the constraint with given `constrid` that belongs to `formulation`. """ getconstr(form::Formulation, id::ConstrId) = get(form.manager.constrs, id, nothing) +""" + getsinglevarconstr(formulation, constrid) -> Constraint + +Returns the single variable constraint with given `constrid` that belongs to `formulation`. +""" +getsinglevarconstr(form::Formulation, id::ConstrId) = get(form.manager.single_var_constrs, id, nothing) + """ getvars(formulation) -> Dict{VarId, Variable} -Return all variables in `formulation`. +Returns all variables in `formulation`. """ getvars(form::Formulation) = form.manager.vars """ getconstrs(formulation) -> Dict{ConstrId, Constraint} -Return all constraints in `formulation`. +Returns all constraints in `formulation`. """ getconstrs(form::Formulation) = form.manager.constrs +""" + getsinglevarconstrs(formulation) -> Dict{ConstrId, SingleVarConstraint} + +Returns all single variable constraints in `formulation`. +""" +getsinglevarconstr(form::Formulation) = form.manager.single_var_constrs + + + +"Returns objective constant of the formulation." getobjconst(form::Formulation) = form.manager.objective_constant + +"Sets objective constant of the formulation." function setobjconst!(form::Formulation, val::Float64) form.manager.objective_constant = val form.buffer.changed_obj_const = true @@ -94,26 +113,30 @@ end "Returns the representation of the coefficient matrix stored in the formulation manager." getcoefmatrix(form::Formulation) = form.manager.coefficients + getprimalsolmatrix(form::Formulation) = form.manager.primal_sols getprimalsolcosts(form::Formulation) = form.manager.primal_sol_costs getdualsolmatrix(form::Formulation) = form.manager.dual_sols getdualsolrhss(form::Formulation) = form.manager.dual_sol_rhss -"Returns the `uid` of `Formulation` `form`." +"Returns the `uid` of a formulation." getuid(form::Formulation) = form.uid -"Returns the objective function sense of `Formulation` `form`." +"Returns the objective function sense of a formulation." getobjsense(form::Formulation) = form.obj_sense -"Returns the `AbstractOptimizer` of `Formulation` `form`." -function getoptimizer(form::Formulation, id::Int) - if id <= 0 && id > length(form.optimizers) +"Returns the optimizer of a formulation at a given position." +function getoptimizer(form::Formulation, pos::Int) + if pos <= 0 && pos > length(form.optimizers) return NoOptimizer() end - return form.optimizers[id] + return form.optimizers[pos] end + +"Returns all the optimizers of a formulation." getoptimizers(form::Formulation) = form.optimizers + getelem(form::Formulation, id::VarId) = getvar(form, id) getelem(form::Formulation, id::ConstrId) = getconstr(form, id) @@ -484,7 +507,7 @@ function setsinglevarconstr!( end c_data = ConstrData(rhs, kind, sense, inc_val, is_active, true) constr = SingleVarConstraint(id, varid, name; constr_data = c_data, moi_index = moi_index) - form.manager.var_bound_constrs[id] = constr + _addconstr!(form.manager, constr) return constr end @@ -732,7 +755,7 @@ function _update_bounds!(form::Formulation, var::Variable, ::Val{Equal}, rhs) end function bounds_propagation!(form::Formulation) - for (_, constr) in form.manager.var_bound_constrs + for (_, constr) in form.manager.single_var_constrs var = getvar(form, constr.varid) _update_bounds!(form, var, Val(constr.curdata.sense), constr.curdata.rhs) end diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index eba6ed32f..6512b70d6 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -12,7 +12,7 @@ DynamicSparseArrays.semaphore_key(::Type{I}) where {I <: Id} = zero(I) mutable struct FormulationManager vars::Dict{VarId, Variable} constrs::Dict{ConstrId, Constraint} - var_bound_constrs::Dict{ConstrId, SingleVarConstraint} # ids of the constraint of type : single variable >= bound + single_var_constrs::Dict{VarId, Dict{ConstrId, SingleVarConstraint}} # ids of the constraint of type : single variable >= bound objective_constant::Float64 coefficients::ConstrVarMatrix # rows = constraints, cols = variables # expressions::VarVarMatrix # cols = variables, rows = expressions (not implemented yet) @@ -31,7 +31,7 @@ function FormulationManager(; custom_families_id = Dict{BD.AbstractCustomData,In return FormulationManager( vars, constrs, - Dict{ConstrId, SingleVarConstraint}(), + Dict{VarId, Dict{ConstrId, SingleVarConstraint}}(), 0.0, dynamicsparse(ConstrId, VarId, Float64), #dynamicsparse(VarId, VarId, Float64; fill_mode = false), @@ -67,6 +67,21 @@ function _addconstr!(m::FormulationManager, constr::Constraint) return end +function _addsinglevarconstr!(m::FormulationManager, constr::SingleVarConstraint) + if !haskey(m.single_var_constrs, constr.varid) + m.single_var_constrs[constr.varid] = Dict{ConstrId, SingleVarConstraint}() + end + if haskey(m.single_var_constrs[constr.varid], constr.id) + name = m.single_var_constrs[constr.varid][constr.id].name + error(string( + "Constraint of id ", constr.id, "exists. Its name is ", name; + " and you want to add a constraint named ", constr.name, "." + )) + end + m.single_var_constrs[constr.varid][constr.id] = constr + return +end + function Base.show(io::IO, m::FormulationManager) println(io, "FormulationManager :") println(io, "> variables : ") diff --git a/src/decomposition.jl b/src/decomposition.jl index a91245235..14c7b5ae7 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -83,6 +83,7 @@ function instantiatesp!( end # Master of Dantzig-Wolfe decomposition +# We clone the variables and the single variable constraints at the same time function instantiate_orig_vars!( masterform::Formulation{DwMaster}, origform::Formulation, @@ -92,10 +93,8 @@ function instantiate_orig_vars!( vars_per_ann = annotations.vars_per_ann for (ann, vars) in vars_per_ann formtype = BD.getformulation(ann) - dectype = BD.getdecomposition(ann) if formtype <: BD.Master - for (id, var) in vars - #duty, explicit = _varexpduty(DwMaster, formtype, dectype) + for (_, var) in vars clonevar!(origform, masterform, masterform, var, MasterPureVar, is_explicit = true) end end diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 8411f7448..d6592d6ba 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -5,7 +5,6 @@ MOI.set(OPTIMIZER, MOI.RawParameter("default_optimizer"), GLPK.Optimizer) const CONFIG = MOIT.TestConfig(atol=1e-6, rtol=1e-6, infeas_certificates = false) - @testset "SolverName" begin @test MOI.get(OPTIMIZER, MOI.SolverName()) == "Coluna" end From d4cccec83ea33cd825cf4d4a044eba43c52ae486 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 14 Aug 2021 19:43:37 +0200 Subject: [PATCH 03/19] wip --- src/MathProg/manager.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index 6512b70d6..bd016305f 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -67,14 +67,14 @@ function _addconstr!(m::FormulationManager, constr::Constraint) return end -function _addsinglevarconstr!(m::FormulationManager, constr::SingleVarConstraint) +function _addconstr!(m::FormulationManager, constr::SingleVarConstraint) if !haskey(m.single_var_constrs, constr.varid) m.single_var_constrs[constr.varid] = Dict{ConstrId, SingleVarConstraint}() end if haskey(m.single_var_constrs[constr.varid], constr.id) name = m.single_var_constrs[constr.varid][constr.id].name error(string( - "Constraint of id ", constr.id, "exists. Its name is ", name; + "Constraint of id ", constr.id, "exists. Its name is ", name, " and you want to add a constraint named ", constr.name, "." )) end From df12d20eb7a6019bc95207bb0cc93cf83908eaa2 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 14 Aug 2021 23:00:05 +0200 Subject: [PATCH 04/19] tests ok --- src/MathProg/MathProg.jl | 2 +- src/MathProg/clone.jl | 22 ++++++++++++++++++++++ src/MathProg/formulation.jl | 31 +++++++++++++++++++++++++++++-- src/MathProg/manager.jl | 15 +++++++++------ src/MathProg/varconstr.jl | 19 ++++++++++++++++--- src/decomposition.jl | 34 +++++++++++++++++++++++++--------- test/bla | 27 --------------------------- test/issues_tests.jl | 2 +- 8 files changed, 103 insertions(+), 49 deletions(-) delete mode 100644 test/bla diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index a8238534c..6616076a3 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -83,7 +83,7 @@ export AbstractFormulation, Formulation, create_formulation!, getreformulation, set_robust_constr_generator!, get_robust_constr_generators, setcol_from_sp_primalsol!, setcut_from_sp_dualsol!, # TODO : merge with setvar! & setconstr set_objective_sense!, clonevar!, cloneconstr!, clonecoeffs!, initialize_optimizer!, - push_optimizer!, getobjconst, setobjconst!, addcustomvars!, addcustomconstrs! + push_optimizer!, getobjconst, setobjconst!, addcustomvars!, addcustomconstrs!, clonesinglevarconstr!, getsinglevarconstrs, getsinglevarconstr # Duties of formulations export Original, DwMaster, BendersMaster, DwSp, BendersSp diff --git a/src/MathProg/clone.jl b/src/MathProg/clone.jl index ef3b8030e..90f6c4655 100644 --- a/src/MathProg/clone.jl +++ b/src/MathProg/clone.jl @@ -49,6 +49,28 @@ function cloneconstr!( id = Id{Constraint}(duty, getid(constr), getuid(assignedform)) ) end + +function clonesinglevarconstr!( + originform::Formulation, + destform::Formulation, + assignedform::Formulation, + constr::SingleVarConstraint, + duty::Duty{Constraint}; + name::String = constr.name, #getname(originform, constr), + rhs::Float64 = constr.perendata.rhs, #getperenrhs(originform, constr), + kind::ConstrKind = constr.perendata.kind, #getperenkind(originform, constr), + sense::ConstrSense = constr.perendata.sense, #getperensense(originform, constr), + inc_val::Float64 = constr.perendata.inc_val, #getperenincval(originform, constr), + is_active::Bool = true, #isperenactive(originform, constr), + is_explicit::Bool = true #isexplicit(originform, constr) +) + return setsinglevarconstr!( + destform, name, constr.varid, duty; + rhs = rhs, kind = kind, sense = sense, inc_val = inc_val, + is_active = is_active, + id = Id{Constraint}(duty, getid(constr), getuid(assignedform)) + ) +end function clonecoeffs!(originform::Formulation, destform::Formulation) dest_matrix = getcoefmatrix(destform) diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 108a75efe..2b083a20c 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -96,10 +96,13 @@ getconstrs(form::Formulation) = form.manager.constrs getsinglevarconstrs(formulation) -> Dict{ConstrId, SingleVarConstraint} Returns all single variable constraints in `formulation`. -""" -getsinglevarconstr(form::Formulation) = form.manager.single_var_constrs + getsinglevarconstrs(formulation, varid) -> Dict{ConstrId, SingleVarConstr} or nothing +Return all single variable constraints of a formulation that applies to a given variable. +""" +getsinglevarconstrs(form::Formulation) = form.manager.single_var_constrs +getsinglevarconstrs(form::Formulation, varid::VarId) = get(form.manager.single_var_constrs_per_var, varid, Dict{ConstrId, SingleVarConstraint}()) "Returns objective constant of the formulation." getobjconst(form::Formulation) = form.manager.objective_constant @@ -810,6 +813,29 @@ function _show_constraints(io::IO , form::Formulation) return end +function _show_singlevarconstraint(io::IO, form::Formulation, constr::SingleVarConstraint) + print(io, "| ", getname(form, constr.varid)) + op = "<=" + if getcursense(form, constr) == Equal + op = "==" + elseif getcursense(form, constr) == Greater + op = ">=" + end + println(io, " ", op, " ", getcurrhs(form, constr)) + return +end + +function _show_singlevarconstraints(io::IO, form::Formulation) + println("----------------------------------") + for (varid, _) in getvars(form) + for (_, constr) in getsinglevarconstrs(form, varid) + _show_singlevarconstraint(io, form, constr) + end + end + println("----------------------------------") + return +end + function _show_variable(io::IO, form::Formulation, var::Variable) name = getname(form, var) lb = getcurlb(form, var) @@ -838,6 +864,7 @@ function Base.show(io::IO, form::Formulation{Duty}) where {Duty <: AbstractFormD _show_obj_fun(io, form) _show_constraints(io, form) _show_variables(io, form) + _show_singlevarconstraints(io, form) end return end diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index bd016305f..27b5dd835 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -12,7 +12,8 @@ DynamicSparseArrays.semaphore_key(::Type{I}) where {I <: Id} = zero(I) mutable struct FormulationManager vars::Dict{VarId, Variable} constrs::Dict{ConstrId, Constraint} - single_var_constrs::Dict{VarId, Dict{ConstrId, SingleVarConstraint}} # ids of the constraint of type : single variable >= bound + single_var_constrs::Dict{ConstrId, SingleVarConstraint} + single_var_constrs_per_var::Dict{VarId, Dict{ConstrId, SingleVarConstraint}} # ids of the constraint of type : single variable >= bound objective_constant::Float64 coefficients::ConstrVarMatrix # rows = constraints, cols = variables # expressions::VarVarMatrix # cols = variables, rows = expressions (not implemented yet) @@ -31,6 +32,7 @@ function FormulationManager(; custom_families_id = Dict{BD.AbstractCustomData,In return FormulationManager( vars, constrs, + Dict{ConstrId, SingleVarConstraint}(), Dict{VarId, Dict{ConstrId, SingleVarConstraint}}(), 0.0, dynamicsparse(ConstrId, VarId, Float64), @@ -68,17 +70,18 @@ function _addconstr!(m::FormulationManager, constr::Constraint) end function _addconstr!(m::FormulationManager, constr::SingleVarConstraint) - if !haskey(m.single_var_constrs, constr.varid) - m.single_var_constrs[constr.varid] = Dict{ConstrId, SingleVarConstraint}() + if !haskey(m.single_var_constrs_per_var, constr.varid) + m.single_var_constrs_per_var[constr.varid] = Dict{ConstrId, SingleVarConstraint}() end - if haskey(m.single_var_constrs[constr.varid], constr.id) - name = m.single_var_constrs[constr.varid][constr.id].name + if haskey(m.single_var_constrs_per_var[constr.varid], constr.id) + name = m.single_var_constrs_per_var[constr.varid][constr.id].name error(string( "Constraint of id ", constr.id, "exists. Its name is ", name, " and you want to add a constraint named ", constr.name, "." )) end - m.single_var_constrs[constr.varid][constr.id] = constr + m.single_var_constrs[constr.id] = constr + m.single_var_constrs_per_var[constr.varid][constr.id] = constr return end diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index 9da925934..e025f0b3a 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -138,6 +138,7 @@ Return the right-hand side as defined by the user of a constraint in a formulati """ getperenrhs(form::Formulation, constrid::ConstrId) = getperenrhs(form, getconstr(form, constrid)) getperenrhs(form::Formulation, constr::Constraint) = constr.perendata.rhs +getperenrhs(form::Formulation, constr::SingleVarConstraint) = constr.perendata.rhs """ getcurrhs(formulation, constraint) @@ -147,6 +148,7 @@ Return the current right-hand side of a constraint in a formulation. """ getcurrhs(form::Formulation, constrid::ConstrId) = getcurrhs(form, getconstr(form, constrid)) getcurrhs(form::Formulation, constr::Constraint) = constr.curdata.rhs +getcurrhs(form::Formulation, constr::SingleVarConstraint) = constr.curdata.rhs """ setperenrhs!(formulation, constr, rhs) @@ -199,6 +201,7 @@ getperenkind(form::Formulation, varid::VarId) = getperenkind(form, getvar(form, getperenkind(form::Formulation, var::Variable) = var.perendata.kind getperenkind(form::Formulation, constrid::ConstrId) = getperenkind(form, getconstr(form, constrid)) getperenkind(form::Formulation, constr::Constraint) = constr.perendata.kind +getperenkind(form::Formulation, constr::SingleVarConstraint) = constr.perendata.kind """ getcurkind(formulation, variable) @@ -239,7 +242,6 @@ function setcurkind!(form::Formulation, var::Variable, kind::VarKind) end setcurkind!(form::Formulation, varid::VarId, kind::VarKind) = setcurkind!(form, getvar(form, varid), kind) - ## sense function _senseofvar(lb::Float64, ub::Float64) lb >= 0 && return Positive @@ -262,6 +264,7 @@ getperensense(form::Formulation, varid::VarId) = getperensense(form, getvar(form getperensense(form::Formulation, var::Variable) = _senseofvar(getperenlb(form, var), getperenub(form, var)) getperensense(form::Formulation, constrid::ConstrId) = getperensense(form, getconstr(form, constrid)) getperensense(form::Formulation, constr::Constraint) = constr.perendata.sense +getperensense(form::Formulation, constr::SingleVarConstraint) = constr.perendata.sense """ getcursense(formulation, varconstr) @@ -274,6 +277,7 @@ getcursense(form::Formulation, varid::VarId) = getcursense(form, getconstr(form, getcursense(form::Formulation, var::Variable) = _senseofvar(getcurlb(form, var), getcurub(form, var)) getcursense(form::Formulation, constrid::ConstrId) = getcursense(form, getconstr(form, constrid)) getcursense(form::Formulation, constr::Constraint) = constr.curdata.sense +getcursense(form::Formulation, constr::SingleVarConstraint) = constr.curdata.sense """ setperensense!(form, constr, sense) @@ -322,6 +326,7 @@ getperenincval(form::Formulation, varid::VarId) = getperenincval(form, getvar(fo getperenincval(form::Formulation, var::Variable) = var.perendata.inc_val getperenincval(form::Formulation, constrid::ConstrId) = getperenincval(form, getconstr(form, constrid)) getperenincval(form::Formulation, constr::Constraint) = constr.perendata.inc_val +getperenincval(form::Formulation, constr::SingleVarConstraint) = constr.perendata.inc_val """ getcurincval(formulation, varconstrid) @@ -333,6 +338,7 @@ getcurincval(form::Formulation, varid::VarId) = getcurincval(form, getvar(form, getcurincval(form::Formulation, var::Variable) = var.curdata.inc_val getcurincval(form::Formulation, constrid::ConstrId) = getcurincval(form, getconstr(form, constrid)) getcurincval(form::Formulation, constr::Constraint) = constr.curdata.inc_val +getcurincval(form::Formulation, constr::SingleVarConstraint) = constr.curdata.inc_val """ setcurincval!(formulation, varconstrid, value::Real) @@ -498,11 +504,18 @@ end function Base.delete!(form::Formulation, constr::SingleVarConstraint) constrid = getid(constr) - delete!(form.manager.single_var_constrs[constr.varid], constrid) + delete!(form.manager.single_var_constrs, constrid) + delete!(form.manager.single_var_constrs_per_var[constr.varid], constrid) return end -Base.delete!(form::Formulation, id::ConstrId) = delete!(form, getconstr(form, id)) +function Base.delete!(form::Formulation, id::ConstrId) + constr = getconstr(form, id) + if constr === nothing + return delete!(form, getsinglevarconstr(form, id)) + end + return delete!(form, constr) +end ## explicit """ diff --git a/src/decomposition.jl b/src/decomposition.jl index 14c7b5ae7..a1dfeabc2 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -96,6 +96,9 @@ function instantiate_orig_vars!( if formtype <: BD.Master for (_, var) in vars clonevar!(origform, masterform, masterform, var, MasterPureVar, is_explicit = true) + for (_, constr) in getsinglevarconstrs(origform, getid(var)) + clonesinglevarconstr!(origform, masterform, masterform, constr, MasterPureConstr) + end end end end @@ -111,7 +114,7 @@ function instantiate_orig_constrs!( ) !haskey(annotations.constrs_per_ann, mast_ann) && return constrs = annotations.constrs_per_ann[mast_ann] - for (id, constr) in constrs + for (_, constr) in constrs cloneconstr!( origform, masterform, masterform, constr, MasterMixedConstr, loc_art_var_abs_cost = env.params.local_art_var_cost @@ -194,11 +197,14 @@ function instantiate_orig_vars!( !haskey(annotations.vars_per_ann, sp_ann) && return vars = annotations.vars_per_ann[sp_ann] masterform = spform.parent_formulation - for (id, var) in vars + for (varid, var) in vars # An original variable annotated in a subproblem is a DwSpPricingVar clonevar!(origform, spform, spform, var, DwSpPricingVar, is_explicit = true) - clonevar!(origform, masterform, spform, var, MasterRepPricingVar, - is_explicit = false)#, members = getcoefmatrix(origform)[:,id]) + for (_, constr) in getsinglevarconstrs(origform, varid) + clonesinglevarconstr!(origform, spform, spform, constr, DwSpPureConstr) + end + + clonevar!(origform, masterform, spform, var, MasterRepPricingVar, is_explicit = false) end return end @@ -256,8 +262,11 @@ function instantiate_orig_vars!( ) !haskey(annotations.vars_per_ann, mast_ann) && return vars = annotations.vars_per_ann[mast_ann] - for (_, var) in vars - clonevar!(origform, masterform, masterform, var, MasterPureVar, is_explicit = true) + for (varid, var) in vars + clonevar!(origform, masterform, masterform, var, MasterPureVar, is_explicit = true) + for (_, constr) in getsinglevarconstrs(origform, varid) + clonesinglevarconstr!(origform, masterform, masterform, constr, MasterPureConstr) + end end return end @@ -315,8 +324,11 @@ function instantiate_orig_vars!( ) if haskey(annotations.vars_per_ann, sp_ann) vars = annotations.vars_per_ann[sp_ann] - for (_, var) in vars + for (varid, var) in vars clonevar!(origform, spform, spform, var, BendSpSepVar, cost = 0.0) + for (_, constr) in getsinglevarconstrs(origform, varid) + clonesinglevarconstr!(origform, spform, spform, constr, BendSpPureConstr) + end end end return @@ -558,10 +570,14 @@ function reformulate!(prob::Problem, annotations::Annotations, env::Env) # Once the original formulation built, we close the "fill mode" of the # coefficient matrix which is a super fast writing mode compared to the default # writing mode of the dynamic sparse matrix. - if getcoefmatrix(prob.original_formulation).fillmode - closefillmode!(getcoefmatrix(prob.original_formulation)) + origform = get_original_formulation(prob) + if getcoefmatrix(origform).fillmode + closefillmode!(getcoefmatrix(origform)) end + # Update the bounds of the original formulation before reformulating + MathProg.bounds_propagation!(origform) + decomposition_tree = annotations.tree if decomposition_tree !== nothing check_annotations(prob, annotations) diff --git a/test/bla b/test/bla deleted file mode 100644 index 4038d4ea8..000000000 --- a/test/bla +++ /dev/null @@ -1,27 +0,0 @@ -* Problem: -* Class: LP -* Rows: 3 -* Columns: 4 -* Non-zeros: 6 -* Format: Free MPS -* -NAME -ROWS - N R0000000 - L cost[3] - L puresecondstage[2] - L tech2[2] -COLUMNS - C0000001 tech2[2] -1 - C0000002 tech2[2] 1 - C0000003 R0000000 -1 cost[3] 1 - y[2] tech2[2] 1 puresecondstage[2] 1 - y[2] cost[3] -1 -RHS - RHS1 cost[3] -1 puresecondstage[2] 1 - RHS1 tech2[2] 1 -BOUNDS - UP BND1 C0000001 1 - UP BND1 C0000002 1 - UP BND1 y[2] 1 -ENDATA diff --git a/test/issues_tests.jl b/test/issues_tests.jl index 209c69460..24f737cd7 100644 --- a/test/issues_tests.jl +++ b/test/issues_tests.jl @@ -258,7 +258,7 @@ function continuous_vars_in_sp() function solve_flow_model(f1_integer, coluna) @axis(M, 1:1) model = BlockDecomposition.BlockModel(coluna, direct_model=true) - @variable(model, f[1:3, m in M]) + @variable(model, f[1:3, m in M] >= 0) if f1_integer JuMP.set_integer(f[1, 1]) end From d3c86ed889de793c0867477253b3ca8ed587ec65 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Mon, 16 Aug 2021 22:38:18 +0200 Subject: [PATCH 05/19] wip --- src/Algorithm/utilities/optimizationstate.jl | 3 - src/ColunaBase/solsandbounds.jl | 24 +++---- src/MOIwrapper.jl | 66 +++++++++++++++----- src/MathProg/MOIinterface.jl | 45 ++++++++----- src/MathProg/clone.jl | 6 +- src/MathProg/constraint.jl | 9 +-- src/MathProg/formulation.jl | 61 +++++++++--------- src/MathProg/solutions.jl | 29 ++++----- src/MathProg/variable.jl | 4 +- src/MathProg/vcids.jl | 23 +++---- test/MathOptInterface/MOI_wrapper.jl | 23 ++----- test/unit/containers/solsandbounds.jl | 6 +- test/unit/variable.jl | 2 +- 13 files changed, 169 insertions(+), 132 deletions(-) diff --git a/src/Algorithm/utilities/optimizationstate.jl b/src/Algorithm/utilities/optimizationstate.jl index 816710209..5fee80999 100644 --- a/src/Algorithm/utilities/optimizationstate.jl +++ b/src/Algorithm/utilities/optimizationstate.jl @@ -1,6 +1,3 @@ -getvalue(sol::Solution) = ColunaBase.getvalue(sol) -getvalue(bnd::Bound) = ColunaBase.getvalue(bnd) - mutable struct OptimizationState{F<:AbstractFormulation,S<:Coluna.AbstractSense} termination_status::TerminationStatus incumbents::ObjValues{S} diff --git a/src/ColunaBase/solsandbounds.jl b/src/ColunaBase/solsandbounds.jl index 88e7ad57d..b5ceaa9d1 100644 --- a/src/ColunaBase/solsandbounds.jl +++ b/src/ColunaBase/solsandbounds.jl @@ -215,12 +215,13 @@ function convert_status(coluna_status::SolutionStatus) end # Solution -struct Solution{Model<:AbstractModel,Decision,Value} <: AbstractDict{Decision,Value} +struct Solution{Model<:AbstractModel,Decision,Value,T} <: AbstractDict{Decision,Value} model::Model bound::Float64 status::SolutionStatus sol::DynamicSparseArrays.PackedMemoryArray{Decision,Value} custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData} + supp_data::T end """ @@ -239,12 +240,13 @@ Create a solution to the `model`. Other arguments are: - `solution_value` is the value of the solution. - `status` is the solution status. """ -function Solution{Mo,De,Va}( +function Solution{Mo,De,Va,T}( model::Mo, decisions::Vector{De}, values::Vector{Va}, solution_value::Float64, - status::SolutionStatus, custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData} = nothing -) where {Mo<:AbstractModel,De,Va} + status::SolutionStatus, custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData}, + supp_data::T +) where {Mo<:AbstractModel,De,Va,T} sol = DynamicSparseArrays.dynamicsparsevec(decisions, values) - return Solution(model, solution_value, status, sol, custom_data) + return Solution(model, solution_value, status, sol, custom_data, supp_data) end """ @@ -271,15 +273,15 @@ getstatus(s::Solution) = s.status Base.iterate(s::Solution) = iterate(s.sol) Base.iterate(s::Solution, state) = iterate(s.sol, state) Base.length(s::Solution) = length(s.sol) -Base.get(s::Solution{Mo,De,Va}, id::De, default) where {Mo,De,Va} = s.sol[id] # TODO : REMOVE -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.get(s::Solution{Mo,De,Va,T}, id::De, default) where {Mo,De,Va,T} = s.sol[id] # TODO : REMOVE +Base.getindex(s::Solution{Mo,De,Va,T}, id::De) where {Mo,De,Va,T} = Base.getindex(s.sol, id) +Base.setindex!(s::Solution{Mo,De,Va,T}, val::Va, id::De) where {Mo,De,Va,T} = s.sol[id] = val function Base.filter(f::Function, s::S) where {S <: Solution} - return S(s.model, s.bound, s.status, filter(f, s.sol), s.custom_data) + return S(s.model, s.bound, s.status, filter(f, s.sol), s.custom_data, s.supp_data) end -function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va}, valcmp=(==)) where {Mo,De,Va} +function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va,T}, valcmp=(==)) where {Mo,De,Va,T} v = get(a, p[1], Base.secret_table_token) if v !== Base.secret_table_token return valcmp(v, p[2]) @@ -287,7 +289,7 @@ 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::IO, solution::Solution{Mo,De,Va,T}) where {Mo,De,Va,T} println(io, "Solution") for (decision, value) in solution println(io, "| ", decision, " = ", value) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 2965cf0fd..22e9ee357 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -17,6 +17,7 @@ const SupportedConstrSets = Union{ # Helper for SingleVariable constraints struct BoundConstraints + varid::VarId lower::Union{Nothing,SingleVarConstraint} upper::Union{Nothing,SingleVarConstraint} eq::Union{Nothing,SingleVarConstraint} @@ -156,7 +157,7 @@ function _constraint_on_variable!( optimizer, form::Formulation, constrid, var::Variable, ::MOI.Integer ) setperenkind!(form, var, Integ) - optimizer.constrs_on_single_var[constrid] = BoundConstraints(nothing, nothing, nothing) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(getid(var), nothing, nothing, nothing) return end @@ -170,7 +171,7 @@ function _constraint_on_variable!( constr2 = setsinglevarconstr!( form, "ub", getid(var), OriginalConstr; sense = Less, rhs = 1.0 ) - optimizer.constrs_on_single_var[constrid] = BoundConstraints(constr1, constr2, nothing) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(getid(var), constr1, constr2, nothing) return end @@ -180,7 +181,7 @@ function _constraint_on_variable!( constr = setsinglevarconstr!( form, "lb", getid(var), OriginalConstr; sense = Greater, rhs = set.lower ) - optimizer.constrs_on_single_var[constrid] = BoundConstraints(constr, nothing, nothing) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(getid(var), constr, nothing, nothing) return end @@ -190,7 +191,7 @@ function _constraint_on_variable!( constr = setsinglevarconstr!( form, "ub", getid(var), OriginalConstr; sense = Less, rhs = set.upper ) - optimizer.constrs_on_single_var[constrid] = BoundConstraints(nothing, constr, nothing) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(getid(var), nothing, constr, nothing) return end @@ -200,7 +201,7 @@ function _constraint_on_variable!( constr = setsinglevarconstr!( form, "eq", getid(var), OriginalConstr; sense = Equal, rhs = set.value ) - optimizer.constrs_on_single_var[constrid] = BoundConstraint(nothing, nothing, constr) + optimizer.constrs_on_single_var[constrid] = BoundConstraint(getid(var), nothing, nothing, constr) return end @@ -213,7 +214,7 @@ function _constraint_on_variable!( constr2 = setsinglevarconstr!( form, "ub", getid(var), OriginalConstr; sense = Less, rhs = set.upper ) - optimizer.constrs_on_single_var[constrid] = BoundConstraints(constr1, constr2, nothing) + optimizer.constrs_on_single_var[constrid] = BoundConstraints(geid(var), constr1, constr2, nothing) return end @@ -928,16 +929,18 @@ end function MOI.get( optimizer::Optimizer, ::MOI.ConstraintPrimal, index::MOI.ConstraintIndex{F,S} ) where {F<:MOI.SingleVariable,S} - varid = get(optimizer.constrs_on_single_var, index, nothing) - if varid === nothing + MOI.throw_if_not_valid(optimizer, index) + bounds = get(optimizer.constrs_on_single_var, index, nothing) + if bounds === nothing @warn "Could not find constraint with id $(index)." return NaN end best_primal_sol = get_best_ip_primal_sol(optimizer.result) - return get(best_primal_sol, varid, 0.0) + return get(best_primal_sol, bounds.varid, 0.0) end function MOI.get(optimizer::Optimizer, ::MOI.ConstraintPrimal, index::MOI.ConstraintIndex) + MOI.throw_if_not_valid(optimizer, index) constrid = get(optimizer.constrs, index, nothing) if constrid === nothing @warn "Could not find constraint with id $(index)." @@ -954,6 +957,7 @@ function MOI.get( optimizer::Optimizer, attr::MOI.ConstraintDual, index::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64}} ) + MOI.throw_if_not_valid(optimizer, index) dualsols = get_lp_dual_sols(optimizer.result) if 1 <= attr.N <= length(dualsols) return get(dualsols[attr.N], getid(optimizer.constrs[index]), 0.0) @@ -961,12 +965,40 @@ function MOI.get( return error("Invalid result index.") end -# function MOI.get( -# optimizer::Optimizer, attr::MOI.ConstraintDual, index::MOI.ConstraintIndex{F,S} -# ) where {F<:MOI.SingleVariable,S} -# return 0.0 -# end +function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.GreaterThan}) + value, activebound = get(dualsol.supp_data, bc.varid, (0.0, MathProg.LOWER)) + if value != 0.0 && activebound != MathProg.LOWER + return 0.0 + end + return value +end + +function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.LessThan}) + value, activebound = get(dualsol.supp_data, bc.varid, (0.0, MathProg.UPPER)) + if value != 0.0 && activebound != MathProg.UPPER + return 0.0 + end + return value +end + +function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.EqualTo}) + value, _ = get(dualsol.supp_data, bc.varid, (0.0, MathProg.LOWER)) + return value +end -# function MOI.get(optimizer::Optimizer, ::MOI.SolveTime) -# return 0.0 -# end \ No newline at end of file +function _singlevarconstrdualval(bc, dualsol, ::Type{S}) where {S} + @warn "single var constr dual not implemented for $S." + return 0.0 +end + +function MOI.get( + optimizer::Optimizer, attr::MOI.ConstraintDual, index::MOI.ConstraintIndex{F,S} +) where {F<:MOI.SingleVariable,S} + MOI.throw_if_not_valid(optimizer, index) + dualsols = get_lp_dual_sols(optimizer.result) + if 1 <= attr.N <= length(dualsols) + single_var_constrs = optimizer.constrs_on_single_var[index] + return _singlevarconstrdualval(single_var_constrs, dualsols[attr.N], S) + end + return error("Invalid result index.") +end \ No newline at end of file diff --git a/src/MathProg/MOIinterface.jl b/src/MathProg/MOIinterface.jl index d7313ce46..7ddacdfbb 100644 --- a/src/MathProg/MOIinterface.jl +++ b/src/MathProg/MOIinterface.jl @@ -39,6 +39,7 @@ function update_bounds_in_optimizer!(form::Formulation, optimizer::MoiOptimizer, MOI.Interval(getcurlb(form, var), getcurub(form, var)) )) end + return end function update_cost_in_optimizer!(form::Formulation, optimizer::MoiOptimizer, var::Variable) @@ -199,7 +200,7 @@ function _getreducedcost(form::Formulation, optimizer, var::Variable) Cannot retrieve reduced cost of variable $varname from formulation solved with optimizer of type $opt. Method returns nothing. """ - return nothing + return end function getreducedcost(form::Formulation, optimizer::MoiOptimizer, var::Variable) @@ -210,7 +211,7 @@ function getreducedcost(form::Formulation, optimizer::MoiOptimizer, var::Variabl No dual solution stored in the optimizer of formulation. Cannot retrieve reduced costs. Method returns nothing. """ - return nothing + return end if !iscuractive(form, var) || !isexplicit(form, var) varname = getname(form, var) @@ -218,7 +219,7 @@ function getreducedcost(form::Formulation, optimizer::MoiOptimizer, var::Variabl Cannot retrieve reduced cost of variable $varname because the variable must be active and explicit. Method returns nothing. """ - return nothing + return end bounds_interval_idx = getbounds(getmoirecord(var)) dualval = MOI.get(inner, MOI.ConstraintDual(1), bounds_interval_idx) @@ -227,6 +228,7 @@ end getreducedcost(form::Formulation, optimizer::MoiOptimizer, varid::VarId) = getreducedcost(form, optimizer, getvar(form, varid)) function get_primal_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formulation} + #println("\e[1;32m ************* \e[00m") inner = getinner(optimizer) nb_primal_sols = MOI.get(inner, MOI.ResultCount()) solutions = PrimalSolution{F}[] @@ -241,22 +243,25 @@ function get_primal_solutions(form::F, optimizer::MoiOptimizer) where {F <: Form iscuractive(form, id) && isexplicit(form, id) || continue moirec = getmoirecord(var) moi_index = getindex(moirec) - kind = _getcolunakind(moirec) + #kind = _getcolunakind(moirec) val = MOI.get(inner, MOI.VariablePrimal(res_idx), moi_index) solcost += val * getcurcost(form, id) val = round(val, digits = Coluna.TOL_DIGITS) if abs(val) > Coluna.TOL - @logmsg LogLevel(-4) string("Var ", var.name , " = ", val) + #@logmsg LogLevel(0) string("Var ", var.name , " = ", val) push!(solvars, id) push!(solvals, val) end end + #println(" > primal_cost = $solcost --- ( MOI: $(MOI.get(inner, MOI.ObjectiveValue(res_idx))))") push!(solutions, PrimalSolution(form, solvars, solvals, solcost, FEASIBLE_SOL)) end + # println("\e[1;32m ************* \e[00m") return solutions end function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formulation} + #println("\e[1;45m ********* \e[00m") inner = getinner(optimizer) nb_dual_sols = MOI.get(inner, MOI.ResultCount()) solutions = DualSolution{F}[] @@ -264,13 +269,15 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul if MOI.get(inner, MOI.DualStatus(res_idx)) != MOI.FEASIBLE_POINT continue end + solcost = getobjconst(form) - solconstrs = Vector{ConstrId}() - solvals = Vector{Float64}() + solconstrs = ConstrId[] + solvals = Float64[] + # Get dual value of constraints for (id, constr) in getconstrs(form) - iscuractive(form, id) && isexplicit(form, id) || continue moi_index = getindex(getmoirecord(constr)) + MOI.is_valid(inner, moi_index) || continue val = MOI.get(inner, MOI.ConstraintDual(res_idx), moi_index) solcost += val * getcurrhs(form, id) val = round(val, digits = Coluna.TOL_DIGITS) @@ -279,22 +286,28 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul push!(solvals, val) end end + # Get reduced cost of variables - for (id, var) in getvars(form) - iscuractive(form, id) && isexplicit(form, id) || continue + var_red_costs = Dict{VarId, Tuple{Float64,ActiveBound}}() + for (varid, var) in getvars(form) moi_bounds_index = getbounds(getmoirecord(var)) + MOI.is_valid(inner, moi_bounds_index) || continue basis_status = MOI.get(inner, MOI.ConstraintBasisStatus(res_idx), moi_bounds_index) val = MOI.get(inner, MOI.ConstraintDual(res_idx), moi_bounds_index) if basis_status == MOI.NONBASIC_AT_LOWER - solcost += val * getcurlb(form, id) + solcost += val * getcurlb(form, varid) + if abs(val) > Coluna.TOL + var_red_costs[varid] = (val, LOWER) + end elseif basis_status == MOI.NONBASIC_AT_UPPER - solcost += val * getcurub(form, id) + solcost += val * getcurub(form, varid) + if abs(val) > Coluna.TOL + var_red_costs[varid] = (val, UPPER) + end end - # TODO : store reduced cost of the variable in the dual constraint ? end - - sense = getobjsense(form) == MaxSense ? -1.0 : 1.0 - push!(solutions, DualSolution(form, solconstrs, solvals, sense * solcost, FEASIBLE_SOL)) + solcost = MOI.get(inner, MOI.DualObjectiveValue(res_idx)) + push!(solutions, DualSolution(form, solconstrs, solvals, solcost, FEASIBLE_SOL; var_red_costs = var_red_costs)) end return solutions end diff --git a/src/MathProg/clone.jl b/src/MathProg/clone.jl index 90f6c4655..2da6115aa 100644 --- a/src/MathProg/clone.jl +++ b/src/MathProg/clone.jl @@ -21,7 +21,7 @@ function clonevar!( cost = cost, lb = lb, ub = ub, kind = kind, inc_val = inc_val, is_active = is_active, is_explicit = is_explicit, branching_priority = branching_priority, members = members, - id = Id{Variable}(duty, getid(var), getuid(assignedform)) + id = Id{Variable}(duty, getid(var), getuid(assignedform), false) ) end @@ -46,7 +46,7 @@ function cloneconstr!( rhs = rhs, kind = kind, sense = sense, inc_val = inc_val, is_active = is_active, is_explicit = is_explicit, members = members, loc_art_var_abs_cost = loc_art_var_abs_cost, - id = Id{Constraint}(duty, getid(constr), getuid(assignedform)) + id = Id{Constraint}(duty, getid(constr), getuid(assignedform), false) ) end @@ -68,7 +68,7 @@ function clonesinglevarconstr!( destform, name, constr.varid, duty; rhs = rhs, kind = kind, sense = sense, inc_val = inc_val, is_active = is_active, - id = Id{Constraint}(duty, getid(constr), getuid(assignedform)) + id = Id{Constraint}(duty, getid(constr), getuid(assignedform), true) ) end diff --git a/src/MathProg/constraint.jl b/src/MathProg/constraint.jl index 277a89371..00215416a 100644 --- a/src/MathProg/constraint.jl +++ b/src/MathProg/constraint.jl @@ -75,17 +75,12 @@ mutable struct SingleVarConstraint <: AbstractVarConstr varid::VarId perendata::ConstrData curdata::ConstrData - moirecord::MoiConstrRecord end function SingleVarConstraint( - id::ConstrId, varid::VarId, name::String; - constr_data = ConstrData(), moi_index::MoiConstrIndex = MoiConstrIndex(), + id::ConstrId, varid::VarId, name::String; constr_data = ConstrData() ) - return SingleVarConstraint( - id, name, varid, constr_data, ConstrData(constr_data), - MoiConstrRecord(index = moi_index) - ) + return SingleVarConstraint(id, name, varid, constr_data, ConstrData(constr_data)) end mutable struct RobustConstraintsGenerator diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 2b083a20c..a9ee1085e 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -143,11 +143,11 @@ getoptimizers(form::Formulation) = form.optimizers getelem(form::Formulation, id::VarId) = getvar(form, id) getelem(form::Formulation, id::ConstrId) = getconstr(form, id) -generatevarid(duty::Duty{Variable}, form::Formulation) = VarId(duty, form.var_counter += 1, getuid(form), -1) +generatevarid(duty::Duty{Variable}, form::Formulation) = VarId(duty, form.var_counter += 1, getuid(form), -1, false) generatevarid( duty::Duty{Variable}, form::Formulation, custom_family_id::Int -) = VarId(duty, form.var_counter += 1, getuid(form), custom_family_id) -generateconstrid(duty::Duty{Constraint}, form::Formulation) = ConstrId(duty, form.constr_counter += 1, getuid(form), -1) +) = VarId(duty, form.var_counter += 1, getuid(form), custom_family_id, false) +generateconstrid(duty::Duty{Constraint}, form::Formulation, flag::Bool) = ConstrId(duty, form.constr_counter += 1, getuid(form), -1, flag) getmaster(form::Formulation{<:AbstractSpDuty}) = form.parent_formulation getreformulation(form::Formulation{<:AbstractMasterDuty}) = form.parent_formulation @@ -211,10 +211,10 @@ function setvar!( ub = (ub > 1.0) ? 1.0 : ub end if getduty(id) != duty - id = VarId(duty, id, -1) + id = VarId(duty, id, -1, false) end if custom_data !== nothing - id = VarId(duty, id, form.manager.custom_families_id[typeof(custom_data)]) + id = VarId(duty, id, form.manager.custom_families_id[typeof(custom_data)], false) end v_data = VarData(cost, lb, ub, kind, inc_val, is_active, is_explicit) var = Variable( @@ -321,7 +321,7 @@ function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool, end ### else not identical to any existing dual sol - new_dual_sol_id = generateconstrid(BendSpDualSol, form) + new_dual_sol_id = generateconstrid(BendSpDualSol, form, false) _adddualsol!(form, new_dual_sol, new_dual_sol_id) return (true, new_dual_sol_id) end @@ -463,14 +463,15 @@ function setconstr!( members = nothing, # todo Union{AbstractDict{VarId,Float64},Nothing} loc_art_var_abs_cost::Float64 = 0.0, custom_data::Union{Nothing, BD.AbstractCustomData} = nothing, - id = generateconstrid(duty, form) + id = generateconstrid(duty, form, false) ) if getduty(id) != duty - id = ConstrId(duty, id, -1) + id = ConstrId(duty, id, -1, false) end if custom_data !== nothing id = ConstrId( - duty, id, custom_family_id = form.manager.custom_families_id[typeof(custom_data)] + duty, id, false, + custom_family_id = form.manager.custom_families_id[typeof(custom_data)] ) end c_data = ConstrData(rhs, kind, sense, inc_val, is_active, is_explicit) @@ -502,14 +503,10 @@ function setsinglevarconstr!( sense::ConstrSense = Greater, inc_val::Float64 = 0.0, is_active::Bool = true, - moi_index::MoiConstrIndex = MoiConstrIndex(), - id = generateconstrid(duty, form) + id = generateconstrid(duty, form, true) ) - if getduty(id) != duty - id = ConstrId(duty, id, -1) - end c_data = ConstrData(rhs, kind, sense, inc_val, is_active, true) - constr = SingleVarConstraint(id, varid, name; constr_data = c_data, moi_index = moi_index) + constr = SingleVarConstraint(id, varid, name; constr_data = c_data) _addconstr!(form.manager, constr) return constr end @@ -725,42 +722,50 @@ end ############################################################################################ # if a new single var constraint has been added, we should call this method -function _update_var_cur_lb!(form::Formulation, var::Variable, lb) +function _update_var_cur_lb!(form::Formulation, var::Variable, lb, constrid) cur_lb = getcurlb(form, var) if cur_lb < lb setcurlb!(form, var, lb) + var.moirecord.lower_bound = constrid + return true end - return + return false end -function _update_var_cur_ub!(form::Formulation, var::Variable, ub) +function _update_var_cur_ub!(form::Formulation, var::Variable, ub, constrid) cur_ub = getcurub(form, var) if cur_ub > ub setcurub!(form, var, ub) + var.moirecord.upper_bound = constrid + return true end - return + return false end -function _update_bounds!(form::Formulation, var::Variable, ::Val{Greater}, rhs) - _update_var_cur_lb!(form, var, rhs) +function _update_bounds!(form::Formulation, var::Variable, ::Val{Greater}, rhs, constrid) + _update_var_cur_lb!(form, var, rhs, constrid) return end -function _update_bounds!(form::Formulation, var::Variable, ::Val{Less}, rhs) - _update_var_cur_ub!(form, var, rhs) +function _update_bounds!(form::Formulation, var::Variable, ::Val{Less}, rhs, constrid) + _update_var_cur_ub!(form, var, rhs, constrid) return end -function _update_bounds!(form::Formulation, var::Variable, ::Val{Equal}, rhs) - _update_var_cur_lb!(form, var, rhs) - _update_var_cur_ub!(form, var, rhs) +function _update_bounds!(form::Formulation, var::Variable, ::Val{Equal}, rhs, constrid) + update_lb = _update_var_cur_lb!(form, var, rhs, constrid) + update_ub = _update_var_cur_ub!(form, var, rhs, constrid) + if !update_lb || !update_ub + error("Bounds propagation determines infeasibility.") + end return end function bounds_propagation!(form::Formulation) - for (_, constr) in form.manager.single_var_constrs + for (constrid, constr) in form.manager.single_var_constrs var = getvar(form, constr.varid) - _update_bounds!(form, var, Val(constr.curdata.sense), constr.curdata.rhs) + rhs = getcurrhs(form, constr) + _update_bounds!(form, var, Val(constr.curdata.sense), rhs, constrid) end return end diff --git a/src/MathProg/solutions.jl b/src/MathProg/solutions.jl index df8419112..641e4aac2 100644 --- a/src/MathProg/solutions.jl +++ b/src/MathProg/solutions.jl @@ -1,21 +1,22 @@ # new structures for solutions # Constructors for Primal & Dual Solutions -const PrimalSolution{M} = Solution{M, VarId, Float64} -const DualSolution{M} = Solution{M, ConstrId, Float64} +const PrimalSolution{M} = Solution{M, VarId, Float64, Nothing} + +@enum ActiveBound LOWER UPPER +const DualSolution{M} = Solution{M, ConstrId, Float64, Dict{VarId, Tuple{Float64, ActiveBound}}} function PrimalSolution( - form::M, decisions::Vector{De}, vals::Vector{Va}, val::Float64, status::SolutionStatus, - custom_data::Union{Nothing, BD.AbstractCustomData} = nothing -) where {M<:AbstractFormulation,De,Va} - return Solution{M,De,Va}(form, decisions, vals, val, status, custom_data) + form::M, decisions, vals, cost, status, custom_data = nothing +) where {M<:AbstractFormulation} + return PrimalSolution{M}(form, decisions, vals, cost, status, custom_data, nothing) end function DualSolution( - form::M, decisions::Vector{De}, vals::Vector{Va}, val::Float64, status::SolutionStatus, - custom_data::Union{Nothing, BD.AbstractCustomData} = nothing -) where {M<:AbstractFormulation,De,Va} - return Solution{M,De,Va}(form, decisions, vals, val, status, custom_data) + form::M, decisions, vals, cost, status, custom_data = nothing; + var_red_costs = Dict{VarId, Tuple{Float64, ActiveBound}}(), +) where {M<:AbstractFormulation} + return DualSolution{M}(form, decisions, vals, cost, status, custom_data, var_red_costs) end function Base.isinteger(sol::Solution) @@ -51,14 +52,14 @@ function contains(sol::DualSolution, f::Function) return false end -function _assert_same_model(sols::NTuple{N, Solution{M, I, Float64}}) where {N,M,I} +function _assert_same_model(sols::NTuple{N, Solution{M, I, Float64,T}}) where {N,M,I,T} for i in 2:length(sols) sols[i-1].model != sols[i].model && return false end return true end -function Base.cat(sols::Solution{M, I, Float64}...) where {M,I} +function Base.cat(sols::Solution{M, I, Float64, T}...) where {M,I,T} _assert_same_model(sols) || error("Cannot concatenate solutions not attached to the same model.") ids = I[] @@ -68,8 +69,8 @@ function Base.cat(sols::Solution{M, I, Float64}...) where {M,I} push!(vals, value) end - return Solution{M,I,Float64}( - sols[1].model, ids, vals, sum(getvalue.(sols)), sols[1].status + return Solution{M,I,Float64,T}( + sols[1].model, ids, vals, sum(getvalue.(sols)), sols[1].status, nothing, T() ) end diff --git a/src/MathProg/variable.jl b/src/MathProg/variable.jl index bf00995c8..a8108e25e 100644 --- a/src/MathProg/variable.jl +++ b/src/MathProg/variable.jl @@ -51,11 +51,13 @@ Structure to hold the pointers to the MOI representation of a Coluna Variable. mutable struct MoiVarRecord index::MoiVarIndex bounds::MoiVarBound + lower_bound::Union{Nothing,Id{<:AbstractVarConstr}} + upper_bound::Union{Nothing,Id{<:AbstractVarConstr}} kind::MoiVarKind end function MoiVarRecord(;index::MoiVarIndex = MoiVarIndex()) - return MoiVarRecord(index, MoiVarBound(), MoiVarKind()) + return MoiVarRecord(index, MoiVarBound(), nothing, nothing, MoiVarKind()) end getindex(record::MoiVarRecord) = record.index diff --git a/src/MathProg/vcids.jl b/src/MathProg/vcids.jl index 587b3b77c..15472ec4c 100644 --- a/src/MathProg/vcids.jl +++ b/src/MathProg/vcids.jl @@ -17,6 +17,7 @@ struct Id{VC <: AbstractVarConstr} assigned_form_uid_in_reformulation::FormId proc_uid::Int8 custom_family_id::Int8 + flag::Bool # TODO _hash::Int end @@ -28,27 +29,27 @@ function _create_hash(uid, origin_form_uid, proc_uid) ) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id) where {VC} +function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id, flag) where {VC} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, flag, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid) where {VC} +function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, flag) where {VC} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, flag, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id) where {VC} +function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id, flag) where {VC} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, flag, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}, assigned_form_uid_in_reformulation) where {VC} - Id{VC}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, id._hash) +function Id{VC}(duty::Duty{VC}, id::Id{VC}, assigned_form_uid_in_reformulation, flag) where {VC} + Id{VC}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, flag, id._hash) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id) where {VC} - Id{VC}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, id._hash) +function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id, flag) where {VC} + Id{VC}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, flag, id._hash) end Base.hash(a::Id, h::UInt) = hash(a._hash, h) @@ -56,7 +57,7 @@ Base.isequal(a::Id{VC}, b::Id{VC}) where {VC} = Base.isequal(a._hash, b._hash) Base.isequal(a::Int, b::Id) = Base.isequal(a, b._hash) Base.isequal(a::Id, b::Int) = Base.isequal(a._hash, b) Base.isless(a::Id{VC}, b::Id{VC}) where {VC} = Base.isless(a._hash, b._hash) -Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), -1, -1, -1, -1, -1, -1) +Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), -1, -1, -1, -1, -1, false, -1) Base.:(<)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash < b._hash Base.:(<=)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash <= b._hash diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index d6592d6ba..572220414 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -130,7 +130,6 @@ const UNSUPPORTED_TESTS = [ "solve_unbounded_model", # default lower bound 0 ] -MathOptInterface.Test.getconstraint const BASIC = [ "add_variable", "solver_name", @@ -174,19 +173,12 @@ const LP_TESTS = [ "solve_affine_lessthan" ] -const CONSTRAINTDUAL_SINGLEVAR = [ - "solve_with_lowerbound", - "solve_singlevariable_obj", - "solve_constant_obj", - "solve_single_variable_dual_max", - "solve_single_variable_dual_min", - "solve_duplicate_terms_obj", - "solve_blank_obj", - "solve_with_upperbound", - "linear1", - "linear2", - "linear10b", - "linear14" +const CONSTRAINTDUAL_SINGLEVAR = String[ + "solve_single_variable_dual_max", # TODO bug + "solve_single_variable_dual_min", # TODO bug + "linear14", # TODO bug + "linear1", # TODO bug + "linear10" # TODO bug ] const UNCOVERED_TERMINATION_STATUS = [ @@ -194,9 +186,6 @@ const UNCOVERED_TERMINATION_STATUS = [ "linear8c" # DUAL_INFEASIBLE or INFEASIBLE_OR_UNBOUNDED required ] -const SET_CONSTRAINTSET = [ -] - @testset "Unit Basic/MIP" begin MOI.set(OPTIMIZER, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveIpForm())) MOIT.unittest(OPTIMIZER, CONFIG, vcat(UNSUPPORTED_TESTS, LP_TESTS, MIP_TESTS)) diff --git a/test/unit/containers/solsandbounds.jl b/test/unit/containers/solsandbounds.jl index e00be69a4..723cbe949 100644 --- a/test/unit/containers/solsandbounds.jl +++ b/test/unit/containers/solsandbounds.jl @@ -283,16 +283,16 @@ function solution_unit() @testset "Solution" begin model = FakeModel() - Solution = CB.Solution{FakeModel,Int,Float64} + Solution = CB.Solution{FakeModel,Int,Float64,Nothing} dict_sol, soldecs, solvals = fake_solution_factory(100) - primal_sol = Solution(model, soldecs, solvals, 12.3, CB.FEASIBLE_SOL) + primal_sol = Solution(model, soldecs, solvals, 12.3, CB.FEASIBLE_SOL, nothing, nothing) test_solution_iterations(primal_sol, dict_sol) @test CB.getvalue(primal_sol) == 12.3 @test CB.getstatus(primal_sol) == CB.FEASIBLE_SOL 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, Coluna.ColunaBase.FEASIBLE_SOL) + primal_sol = Solution(model, collect(keys(dict_sol)), collect(values(dict_sol)), 0.0, Coluna.ColunaBase.FEASIBLE_SOL, nothing, nothing) @test iterate(primal_sol) == iterate(primal_sol.sol) _, state = iterate(primal_sol) diff --git a/test/unit/variable.jl b/test/unit/variable.jl index 3673fed37..d43da15f7 100644 --- a/test/unit/variable.jl +++ b/test/unit/variable.jl @@ -33,7 +33,7 @@ function variable_getters_and_setters_tests() ) v = ClF.Variable( - ClF.Id{ClF.Variable}(ClF.MasterPureVar, 23, 10), "fake_var"; + ClF.Id{ClF.Variable}(ClF.MasterPureVar, 23, 10, false), "fake_var"; var_data = v_data ) From 693667abd1c1a3382d25a3eb247b29cd9add571a Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Mon, 30 Aug 2021 14:12:31 +0200 Subject: [PATCH 06/19] wip --- Project.toml | 2 ++ src/MathProg/vcids.jl | 2 +- src/decomposition.jl | 5 ++--- test/custom_var_cuts_tests.jl | 11 +++++++++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 688e37ecf..9e2777cef 100644 --- a/Project.toml +++ b/Project.toml @@ -5,10 +5,12 @@ version = "0.4.0" [deps] BlockDecomposition = "6cde8614-403a-11e9-12f1-c10d0f0caca0" +ColunaDemos = "a54e61d4-7723-11e9-2469-af255fcaa246" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" DynamicSparseArrays = "8086fd22-9a0c-46a5-a6c8-6e24676501fe" +KnapsackLib = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" diff --git a/src/MathProg/vcids.jl b/src/MathProg/vcids.jl index 15472ec4c..a767303cd 100644 --- a/src/MathProg/vcids.jl +++ b/src/MathProg/vcids.jl @@ -48,7 +48,7 @@ function Id{VC}(duty::Duty{VC}, id::Id{VC}, assigned_form_uid_in_reformulation, Id{VC}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, flag, id._hash) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id, flag) where {VC} +function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id, flag = false) where {VC} Id{VC}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, flag, id._hash) end diff --git a/src/decomposition.jl b/src/decomposition.jl index a1dfeabc2..fc0a63b32 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -253,7 +253,6 @@ function _dutyexpofbendmastvar( end # Master of Benders decomposition - function instantiate_orig_vars!( masterform::Formulation{BendersMaster}, origform::Formulation{Original}, @@ -308,7 +307,7 @@ function create_side_vars_constrs!( ub = getperenub(spform, nu_var), kind = Continuous, is_explicit = true, - id = Id{Variable}(MasterBendSecondStageCostVar, getid(nu_var), getuid(masterform)) + id = Id{Variable}(MasterBendSecondStageCostVar, getid(nu_var), getuid(masterform), false) ) end return @@ -386,7 +385,7 @@ function create_side_vars_constrs!( ub = getcurub(origform, var), kind = Continuous, is_explicit = true, - id = Id{Variable}(BendSpPosSlackFirstStageVar, varid, getuid(masterform)) + id = Id{Variable}(BendSpPosSlackFirstStageVar, varid, getuid(masterform), false) ) name = string("μ⁻[", split(getname(origform, var), "[")[end], "]") diff --git a/test/custom_var_cuts_tests.jl b/test/custom_var_cuts_tests.jl index 4fb31981d..4e4a70815 100644 --- a/test/custom_var_cuts_tests.jl +++ b/test/custom_var_cuts_tests.jl @@ -74,7 +74,7 @@ function custom_var_cuts_test() # Get the dual values of the custom cuts custduals = Tuple{Int, Float64}[] - for (_, constr) in getconstrs(cbdata.form.parent_formulation) + for (_, constr) in Coluna.MathProg.getconstrs(cbdata.form.parent_formulation) if typeof(constr.custom_data) == MyCustomCutData push!(custduals, ( constr.custom_data.min_items, @@ -122,6 +122,7 @@ function custom_var_cuts_test() solver = my_pricing_callback ) + cut_ids = [] function custom_cut_sep(cbdata) # compute the constraint violation viol = -1.0 @@ -136,10 +137,11 @@ function custom_var_cuts_test() # add the cut (at most one variable with 2 or more of the 3 items) if violated if viol > 0.001 - MOI.submit( + cut_id = MOI.submit( model, MOI.UserCut(cbdata), JuMP.ScalarConstraint(JuMP.AffExpr(0.0), MOI.LessThan(1.0)), MyCustomCutData(2) ) + push!(cut_ids, cut_id) end return end @@ -148,6 +150,11 @@ function custom_var_cuts_test() JuMP.optimize!(model) @show JuMP.objective_value(model) @test JuMP.termination_status(model) == MOI.OPTIMAL + + dual_sol = Coluna.get_best_lp_dual_sol(JuMP.unsafe_backend(model).result) + for id in cut_ids + @show dual_sol[id] + end end end From 2161758b2541738fdbce35a52a5fabdf9513fe21 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Mon, 30 Aug 2021 23:42:07 +0200 Subject: [PATCH 07/19] wip --- src/Algorithm/colgen.jl | 3 +- src/Algorithm/treesearch.jl | 1 + src/MathProg/MOIinterface.jl | 18 +++++------- test/MathOptInterface/MOI_wrapper.jl | 44 ++++++++++++++-------------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index d81e8531a..1d762ef2c 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -799,8 +799,9 @@ function cg_main_loop!( if nb_new_columns == 0 && !essential_cuts_separated @logmsg LogLevel(0) "No new column generated by the pricing problems." setterminationstatus!(cg_optstate, OTHER_LIMIT) - # if no columns are generated and lp gap is not closed then this col.gen. stage + # If no columns are generated and lp gap is not closed then this col.gen. stage # is a heuristic one, so we do not run phase 1 to save time + # Comment by @guimarqu : It may also be a bug return true, false end if iteration > algo.max_nb_iterations diff --git a/src/Algorithm/treesearch.jl b/src/Algorithm/treesearch.jl index 1f9f8b966..7934e4fc7 100644 --- a/src/Algorithm/treesearch.jl +++ b/src/Algorithm/treesearch.jl @@ -318,6 +318,7 @@ function updatedualbound!(data::TreeSearchRuntimeData) worst_bound = data.worst_db_of_pruned_node end set_ip_dual_bound!(treestate, worst_bound) + set_lp_dual_bound!(treestate, worst_bound) return end diff --git a/src/MathProg/MOIinterface.jl b/src/MathProg/MOIinterface.jl index 7ddacdfbb..d031cf1e4 100644 --- a/src/MathProg/MOIinterface.jl +++ b/src/MathProg/MOIinterface.jl @@ -228,7 +228,6 @@ end getreducedcost(form::Formulation, optimizer::MoiOptimizer, varid::VarId) = getreducedcost(form, optimizer, getvar(form, varid)) function get_primal_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formulation} - #println("\e[1;32m ************* \e[00m") inner = getinner(optimizer) nb_primal_sols = MOI.get(inner, MOI.ResultCount()) solutions = PrimalSolution{F}[] @@ -236,32 +235,30 @@ function get_primal_solutions(form::F, optimizer::MoiOptimizer) where {F <: Form if MOI.get(inner, MOI.PrimalStatus(res_idx)) != MOI.FEASIBLE_POINT continue end + solcost = getobjconst(form) - solvars = Vector{VarId}() - solvals = Vector{Float64}() + solvars = VarId[] + solvals = Float64[] + + # Get primal values of variables for (id, var) in getvars(form) iscuractive(form, id) && isexplicit(form, id) || continue moirec = getmoirecord(var) moi_index = getindex(moirec) - #kind = _getcolunakind(moirec) val = MOI.get(inner, MOI.VariablePrimal(res_idx), moi_index) solcost += val * getcurcost(form, id) val = round(val, digits = Coluna.TOL_DIGITS) if abs(val) > Coluna.TOL - #@logmsg LogLevel(0) string("Var ", var.name , " = ", val) push!(solvars, id) push!(solvals, val) end end - #println(" > primal_cost = $solcost --- ( MOI: $(MOI.get(inner, MOI.ObjectiveValue(res_idx))))") push!(solutions, PrimalSolution(form, solvars, solvals, solcost, FEASIBLE_SOL)) end - # println("\e[1;32m ************* \e[00m") return solutions end function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formulation} - #println("\e[1;45m ********* \e[00m") inner = getinner(optimizer) nb_dual_sols = MOI.get(inner, MOI.ResultCount()) solutions = DualSolution{F}[] @@ -306,8 +303,9 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul end end end - solcost = MOI.get(inner, MOI.DualObjectiveValue(res_idx)) - push!(solutions, DualSolution(form, solconstrs, solvals, solcost, FEASIBLE_SOL; var_red_costs = var_red_costs)) + push!(solutions, DualSolution( + form, solconstrs, solvals, solcost, FEASIBLE_SOL; var_red_costs = var_red_costs + )) end return solutions end diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 572220414..6543dd7f0 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -87,28 +87,28 @@ end @test JuMP.objective_value(model) == -JuMP.objective_value(model2) end -@testset "SplitIntervalBridge" begin - coluna = optimizer_with_attributes( - Coluna.Optimizer, - "params" => Coluna.Params( - solver=Coluna.Algorithm.TreeSearchAlgorithm() - ), - "default_optimizer" => GLPK.Optimizer - ) - - @axis(M, 1:1) - J = 1:1 - - model = BlockModel(coluna) - @variable(model, x[m in M, j in J]) - @constraint(model, mult[m in M], 1 <= sum(x[m,j] for j in J) <= 2) - @objective(model, Max, sum(x[m,j] for m in M, j in J)) - - @dantzig_wolfe_decomposition(model, decomposition, M) - - optimize!(model) - @test JuMP.objective_value(model) == 2.0 -end +# @testset "SplitIntervalBridge" begin +# coluna = optimizer_with_attributes( +# Coluna.Optimizer, +# "params" => Coluna.Params( +# solver=Coluna.Algorithm.TreeSearchAlgorithm() +# ), +# "default_optimizer" => GLPK.Optimizer +# ) + +# @axis(M, 1:1) +# J = 1:1 + +# model = BlockModel(coluna) +# @variable(model, x[m in M, j in J]) +# @constraint(model, mult[m in M], 1 <= sum(x[m,j] for j in J) <= 2) +# @objective(model, Max, sum(x[m,j] for m in M, j in J)) + +# @dantzig_wolfe_decomposition(model, decomposition, M) + +# optimize!(model) +# @test JuMP.objective_value(model) == 2.0 +# end const UNSUPPORTED_TESTS = [ "solve_qcp_edge_cases", # Quadratic constraints not supported From 7d54c35845f6c03becd50ea3fd32a88d007b3886 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Tue, 31 Aug 2021 22:27:22 +0200 Subject: [PATCH 08/19] v1 --- src/Algorithm/benders.jl | 6 ++-- src/Algorithm/node.jl | 2 +- src/MOIwrapper.jl | 2 +- src/MathProg/MOIinterface.jl | 23 +++++++++--- src/MathProg/MathProg.jl | 2 +- src/MathProg/clone.jl | 6 ++-- src/MathProg/constraint.jl | 2 +- src/MathProg/formulation.jl | 54 ++++++++++++++++++---------- src/MathProg/manager.jl | 3 ++ src/MathProg/vcids.jl | 33 ++++++++--------- src/decomposition.jl | 4 +-- test/MathOptInterface/MOI_wrapper.jl | 1 - test/custom_var_cuts_tests.jl | 6 ---- 13 files changed, 83 insertions(+), 61 deletions(-) diff --git a/src/Algorithm/benders.jl b/src/Algorithm/benders.jl index 416824506..9817af2fe 100644 --- a/src/Algorithm/benders.jl +++ b/src/Algorithm/benders.jl @@ -190,7 +190,7 @@ function insert_cuts_in_master!( name = string("BC_", getsortuid(dual_sol_id)) kind = Essential duty = MasterBendCutConstr - bc = setcut_from_sp_dualsol!( + setcut_from_sp_dualsol!( masterform, spform, dual_sol_id, @@ -233,7 +233,6 @@ function solve_sp_to_gencut!( benders_sp_primal_bound_contrib = 0.0 benders_sp_lagrangian_bound_contrib = 0.0 - insertion_status = 0 spsol_relaxed = false # Compute target @@ -363,7 +362,7 @@ function solve_sps_to_gencuts!( ### BEGIN LOOP TO BE PARALLELIZED for (spuid, spform) in sps - recorded_sp_dual_solution_ids[spuid] = Vector{ConstrId}() + recorded_sp_dual_solution_ids[spuid] = ConstrId[] gen_status, spsol_relaxed, recorded_dual_solution_ids, benders_sp_primal_bound_contrib, benders_sp_lagrangian_bound_contrib = solve_sp_to_gencut!( algo, env, algdata, masterform, spform, master_primalsol, master_dualsol, @@ -559,7 +558,6 @@ function bend_cutting_plane_main_loop!( setterminationstatus!(bnd_optstate, OPTIMAL) break # loop on master lp solution end - end # loop on master lp solution if !one_spsol_is_a_relaxed_sol diff --git a/src/Algorithm/node.jl b/src/Algorithm/node.jl index d3b9da79f..3eb9230f5 100644 --- a/src/Algorithm/node.jl +++ b/src/Algorithm/node.jl @@ -8,7 +8,7 @@ mutable struct Node depth::Int parent::Union{Nothing, Node} optstate::OptimizationState - #branch::Union{Nothing, Branch} # branch::Id{Constraint} + #branch::Union{Nothing, Branch} # branch::ConstrId branchdescription::String recordids::RecordsVector conquerwasrun::Bool diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 22e9ee357..10529baa8 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -885,7 +885,7 @@ function MOI.get(optimizer::Optimizer, ::MOI.RelativeGap) end function MOI.get(optimizer::Optimizer, attr::MOI.VariablePrimal, ref::MOI.VariableIndex) - id = getid(optimizer.vars[ref]) # This gets a coluna Id{Variable} + id = getid(optimizer.vars[ref]) # This gets a coluna VarId primalsols = get_ip_primal_sols(optimizer.result) if 1 <= attr.N <= length(primalsols) return get(primalsols[attr.N], id, 0.0) diff --git a/src/MathProg/MOIinterface.jl b/src/MathProg/MOIinterface.jl index d031cf1e4..b8e3bb28a 100644 --- a/src/MathProg/MOIinterface.jl +++ b/src/MathProg/MOIinterface.jl @@ -258,20 +258,25 @@ function get_primal_solutions(form::F, optimizer::MoiOptimizer) where {F <: Form return solutions end +# Retrieve dual solutions stored in the optimizer of a formulation +# It works only if the optimizer is wrapped with MathOptInterface. function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formulation} inner = getinner(optimizer) nb_dual_sols = MOI.get(inner, MOI.ResultCount()) solutions = DualSolution{F}[] + for res_idx in 1:nb_dual_sols + # We retrieve only feasible dual solutions if MOI.get(inner, MOI.DualStatus(res_idx)) != MOI.FEASIBLE_POINT continue end + # Cost of the dual solution solcost = getobjconst(form) - solconstrs = ConstrId[] - solvals = Float64[] # Get dual value of constraints + solconstrs = ConstrId[] + solvals = Float64[] for (id, constr) in getconstrs(form) moi_index = getindex(getmoirecord(constr)) MOI.is_valid(inner, moi_index) || continue @@ -284,13 +289,16 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul end end - # Get reduced cost of variables + # Get dual value & active bound of variables var_red_costs = Dict{VarId, Tuple{Float64,ActiveBound}}() for (varid, var) in getvars(form) moi_bounds_index = getbounds(getmoirecord(var)) MOI.is_valid(inner, moi_bounds_index) || continue basis_status = MOI.get(inner, MOI.ConstraintBasisStatus(res_idx), moi_bounds_index) val = MOI.get(inner, MOI.ConstraintDual(res_idx), moi_bounds_index) + + # Variables with non-zero dual values have at least one active bound. + # Otherwise, we print a warning message. if basis_status == MOI.NONBASIC_AT_LOWER solcost += val * getcurlb(form, varid) if abs(val) > Coluna.TOL @@ -301,10 +309,17 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul if abs(val) > Coluna.TOL var_red_costs[varid] = (val, UPPER) end + elseif abs(val) > Coluna.TOL + @warn """ + Basis status of a variable that has a non-zero dual value is not treated. + Basis status is $basis_status & dual value is $val. + """ end end + + sense = getobjsense(form) == MinSense ? 1.0 : -1.0 push!(solutions, DualSolution( - form, solconstrs, solvals, solcost, FEASIBLE_SOL; var_red_costs = var_red_costs + form, solconstrs, solvals, sense*solcost, FEASIBLE_SOL; var_red_costs = var_red_costs )) end return solutions diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index 6616076a3..b5d3a0ddf 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -28,10 +28,10 @@ include("vcids.jl") include("variable.jl") include("constraint.jl") include("duties.jl") -include("manager.jl") include("bounds.jl") include("solutions.jl") include("buffer.jl") +include("manager.jl") include("formulation.jl") include("varconstr.jl") include("optimizerwrappers.jl") diff --git a/src/MathProg/clone.jl b/src/MathProg/clone.jl index 2da6115aa..d73611686 100644 --- a/src/MathProg/clone.jl +++ b/src/MathProg/clone.jl @@ -21,7 +21,7 @@ function clonevar!( cost = cost, lb = lb, ub = ub, kind = kind, inc_val = inc_val, is_active = is_active, is_explicit = is_explicit, branching_priority = branching_priority, members = members, - id = Id{Variable}(duty, getid(var), getuid(assignedform), false) + id = VarId(duty, getid(var), getuid(assignedform)) ) end @@ -46,7 +46,7 @@ function cloneconstr!( rhs = rhs, kind = kind, sense = sense, inc_val = inc_val, is_active = is_active, is_explicit = is_explicit, members = members, loc_art_var_abs_cost = loc_art_var_abs_cost, - id = Id{Constraint}(duty, getid(constr), getuid(assignedform), false) + id = ConstrId(duty, getid(constr), getuid(assignedform)) ) end @@ -68,7 +68,7 @@ function clonesinglevarconstr!( destform, name, constr.varid, duty; rhs = rhs, kind = kind, sense = sense, inc_val = inc_val, is_active = is_active, - id = Id{Constraint}(duty, getid(constr), getuid(assignedform), true) + id = ConstrId(duty, getid(constr), getuid(assignedform)) ) end diff --git a/src/MathProg/constraint.jl b/src/MathProg/constraint.jl index 00215416a..d7b7cb8ed 100644 --- a/src/MathProg/constraint.jl +++ b/src/MathProg/constraint.jl @@ -70,7 +70,7 @@ function Constraint( end mutable struct SingleVarConstraint <: AbstractVarConstr - id::Id{Constraint} + id::ConstrId name::String varid::VarId perendata::ConstrData diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index a9ee1085e..d6fcb171c 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -143,11 +143,11 @@ getoptimizers(form::Formulation) = form.optimizers getelem(form::Formulation, id::VarId) = getvar(form, id) getelem(form::Formulation, id::ConstrId) = getconstr(form, id) -generatevarid(duty::Duty{Variable}, form::Formulation) = VarId(duty, form.var_counter += 1, getuid(form), -1, false) +generatevarid(duty::Duty{Variable}, form::Formulation) = VarId(duty, form.var_counter += 1, getuid(form), -1) generatevarid( duty::Duty{Variable}, form::Formulation, custom_family_id::Int -) = VarId(duty, form.var_counter += 1, getuid(form), custom_family_id, false) -generateconstrid(duty::Duty{Constraint}, form::Formulation, flag::Bool) = ConstrId(duty, form.constr_counter += 1, getuid(form), -1, flag) +) = VarId(duty, form.var_counter += 1, getuid(form), custom_family_id) +generateconstrid(duty::Duty{Constraint}, form::Formulation) = ConstrId(duty, form.constr_counter += 1, getuid(form), -1) getmaster(form::Formulation{<:AbstractSpDuty}) = form.parent_formulation getreformulation(form::Formulation{<:AbstractMasterDuty}) = form.parent_formulation @@ -158,7 +158,7 @@ getstoragedict(form::Formulation) = form.storage.units _reset_buffer!(form::Formulation) = form.buffer = FormulationBuffer() """ - set_matrix_coeff!(form::Formulation, v_id::Id{Variable}, c_id::Id{Constraint}, new_coeff::Float64) + set_matrix_coeff!(form::Formulation, v_id::VarId, c_id::ConstrId, new_coeff::Float64) Buffers the matrix modification in `form.buffer` to be sent to the optimizers right before next call to optimize!. """ @@ -211,10 +211,10 @@ function setvar!( ub = (ub > 1.0) ? 1.0 : ub end if getduty(id) != duty - id = VarId(duty, id, -1, false) + id = VarId(duty, id, -1) end if custom_data !== nothing - id = VarId(duty, id, form.manager.custom_families_id[typeof(custom_data)], false) + id = VarId(duty, id, form.manager.custom_families_id[typeof(custom_data)]) end v_data = VarData(cost, lb, ub, kind, inc_val, is_active, is_explicit) var = Variable( @@ -285,6 +285,14 @@ function _adddualsol!(form::Formulation, dualsol::DualSolution, dualsol_id::Cons form.manager.dual_sols[constrid, dualsol_id] = constrval end end + for (varid, varval) in dualsol.supp_data + redcost, activebound = varval + bound = activebound == LOWER ? getcurlb(form, varid) : getcurub(form, varid) + rhs += bound * redcost + if getduty(varid) <= AbstractBendSpMasterConstr + form.manager.dual_sols_varbounds[varid, dualsol_id] = varval + end + end form.manager.dual_sol_rhss[dualsol_id] = rhs return dualsol_id end @@ -292,6 +300,7 @@ end function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool,ConstrId} ### check if dualsol exists take place here along the coeff update dual_sols = getdualsolmatrix(form) + dual_sols_varbounds = form.manager.dual_sols_varbounds dual_sol_rhss = getdualsolrhss(form) for (cur_sol_id, cur_rhs) in dual_sol_rhss @@ -302,7 +311,7 @@ function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool, # TODO : implement broadcasting for PMA in DynamicSparseArrays is_identical = true - cur_dual_sol = dual_sols[cur_sol_id, :] + cur_dual_sol = @view dual_sols[:,cur_sol_id] for (constr_id, constr_val) in cur_dual_sol if factor * getsol(new_dual_sol)[constr_id] != constr_val is_identical = false @@ -310,8 +319,9 @@ function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool, end end - for (constr_id, constr_val) in getsol(new_dual_sol) - if factor * constr_val != cur_dual_sol[constr_id] + cur_dual_sol_varbounds = @view dual_sols_varbounds[:,cur_sol_id] + for (var_id, var_val) in cur_dual_sol_varbounds + if factor * new_dual_sol.supp_data[var_id][1] != var_val[1] || new_dual_sol.supp_data[var_id][2] != var_val[2] is_identical = false break end @@ -321,7 +331,7 @@ function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool, end ### else not identical to any existing dual sol - new_dual_sol_id = generateconstrid(BendSpDualSol, form, false) + new_dual_sol_id = generateconstrid(BendSpDualSol, form) _adddualsol!(form, new_dual_sol, new_dual_sol_id) return (true, new_dual_sol_id) end @@ -380,7 +390,7 @@ function setcut_from_sp_dualsol!( objc = getobjsense(masterform) === MinSense ? 1.0 : -1.0 rhs = objc * getdualsolrhss(spform)[dual_sol_id] - benders_cut_id = Id{Constraint}(duty, dual_sol_id) + benders_cut_id = ConstrId(duty, dual_sol_id) benders_cut_data = ConstrData( rhs, Essential, sense, inc_val, is_active, is_explicit ) @@ -419,6 +429,14 @@ function setcut_from_sp_dualsol!( end end + # sp_dual_sol = spform.manager.dual_sols_varbounds[:,dual_sol_id] + # for (var_id, var_val) in sp_dual_sol + # var_val, active_bound = var_val + # if getduty(var_id) <= AbstractBendSpVar + # orig_var_id + # end + # end + _addconstr!(masterform.manager, benders_cut) if isexplicit(masterform, benders_cut) @@ -463,14 +481,14 @@ function setconstr!( members = nothing, # todo Union{AbstractDict{VarId,Float64},Nothing} loc_art_var_abs_cost::Float64 = 0.0, custom_data::Union{Nothing, BD.AbstractCustomData} = nothing, - id = generateconstrid(duty, form, false) + id = generateconstrid(duty, form) ) if getduty(id) != duty - id = ConstrId(duty, id, -1, false) + id = ConstrId(duty, id, -1) end if custom_data !== nothing id = ConstrId( - duty, id, false, + duty, id, custom_family_id = form.manager.custom_families_id[typeof(custom_data)] ) end @@ -503,7 +521,7 @@ function setsinglevarconstr!( sense::ConstrSense = Greater, inc_val::Float64 = 0.0, is_active::Bool = true, - id = generateconstrid(duty, form, true) + id = generateconstrid(duty, form) ) c_data = ConstrData(rhs, kind, sense, inc_val, is_active, true) constr = SingleVarConstraint(id, varid, name; constr_data = c_data) @@ -671,13 +689,13 @@ function set_objective_sense!(form::Formulation, min::Bool) return end -function computesolvalue(form::Formulation, sol_vec::AbstractDict{Id{Variable}, Float64}) +function computesolvalue(form::Formulation, sol_vec::AbstractDict{VarId, Float64}) val = sum(getperencost(form, varid) * value for (varid, value) in sol_vec) return val end # TODO : remove (unefficient & specific to an algorithm) -function computereducedcost(form::Formulation, varid::Id{Variable}, dualsol::DualSolution) +function computereducedcost(form::Formulation, varid::VarId, dualsol::DualSolution) redcost = getperencost(form, varid) coefficient_matrix = getcoefmatrix(form) sign = 1 @@ -692,7 +710,7 @@ function computereducedcost(form::Formulation, varid::Id{Variable}, dualsol::Dua end # TODO : remove (unefficient & specific to Benders) -function computereducedrhs(form::Formulation{BendersSp}, constrid::Id{Constraint}, primalsol::PrimalSolution) +function computereducedrhs(form::Formulation{BendersSp}, constrid::ConstrId, primalsol::PrimalSolution) constrrhs = getperenrhs(form,constrid) coefficient_matrix = getcoefmatrix(form) for (varid, primal_val) in primalsol diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index 27b5dd835..ea3a9556e 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -4,6 +4,7 @@ const VarMembership = Dict{VarId, Float64} const ConstrMembership = Dict{ConstrId, Float64} const ConstrConstrMatrix = DynamicSparseArrays.DynamicSparseMatrix{ConstrId,ConstrId,Float64} const ConstrVarMatrix = DynamicSparseArrays.DynamicSparseMatrix{ConstrId,VarId,Float64} +const VarConstrDualSolMatrix = DynamicSparseArrays.DynamicSparseMatrix{VarId,ConstrId,Tuple{Float64,ActiveBound}} const VarVarMatrix = DynamicSparseArrays.DynamicSparseMatrix{VarId,VarId,Float64} # Define the semaphore of the dynamic sparse matrix using MathProg.Id as index @@ -21,6 +22,7 @@ mutable struct FormulationManager primal_sols_custom_data::Dict{VarId, BD.AbstractCustomData} primal_sol_costs::DynSparseVector{VarId} # primal solutions with varid map to their cost 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 robust_constr_generators::Vector{RobustConstraintsGenerator} custom_families_id::Dict{DataType,Int} @@ -41,6 +43,7 @@ function FormulationManager(; custom_families_id = Dict{BD.AbstractCustomData,In Dict{VarId,Any}(), dynamicsparsevec(VarId[], Float64[]), dynamicsparse(ConstrId, ConstrId, Float64; fill_mode = false), + dynamicsparse(VarId, ConstrId, Tuple{Float64, ActiveBound}; fill_mode = false), dynamicsparsevec(ConstrId[], Float64[]), RobustConstraintsGenerator[], custom_families_id diff --git a/src/MathProg/vcids.jl b/src/MathProg/vcids.jl index a767303cd..59d002019 100644 --- a/src/MathProg/vcids.jl +++ b/src/MathProg/vcids.jl @@ -17,39 +17,34 @@ struct Id{VC <: AbstractVarConstr} assigned_form_uid_in_reformulation::FormId proc_uid::Int8 custom_family_id::Int8 - flag::Bool # TODO _hash::Int end -function _create_hash(uid, origin_form_uid, proc_uid) - return ( - uid * MAX_NB_FORMULATIONS * MAX_NB_PROCESSES - + origin_form_uid * MAX_NB_PROCESSES - + proc_uid - ) -end +_create_hash(uid, origin_form_uid, proc_uid) = + uid * MAX_NB_FORMULATIONS * MAX_NB_PROCESSES + + origin_form_uid * MAX_NB_PROCESSES + proc_uid -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id, flag) where {VC} +function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id) where {VC} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, flag, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, flag) where {VC} +function Id{VC}(duty::Duty{VC}, uid, origin_form_uid) where {VC} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, flag, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id, flag) where {VC} +function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id) where {VC} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, flag, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}, assigned_form_uid_in_reformulation, flag) where {VC} - Id{VC}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, flag, id._hash) +function Id{VC}(duty::Duty{VC}, id::Id{VC}, assigned_form_uid_in_reformulation) where {VC} + Id{VC}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, id._hash) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id, flag = false) where {VC} - Id{VC}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, flag, id._hash) +function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id) where {VC} + Id{VC}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, id._hash) end Base.hash(a::Id, h::UInt) = hash(a._hash, h) @@ -57,7 +52,7 @@ Base.isequal(a::Id{VC}, b::Id{VC}) where {VC} = Base.isequal(a._hash, b._hash) Base.isequal(a::Int, b::Id) = Base.isequal(a, b._hash) Base.isequal(a::Id, b::Int) = Base.isequal(a._hash, b) Base.isless(a::Id{VC}, b::Id{VC}) where {VC} = Base.isless(a._hash, b._hash) -Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), -1, -1, -1, -1, -1, false, -1) +Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), -1, -1, -1, -1, -1, -1) Base.:(<)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash < b._hash Base.:(<=)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash <= b._hash diff --git a/src/decomposition.jl b/src/decomposition.jl index fc0a63b32..e15d98aff 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -307,7 +307,7 @@ function create_side_vars_constrs!( ub = getperenub(spform, nu_var), kind = Continuous, is_explicit = true, - id = Id{Variable}(MasterBendSecondStageCostVar, getid(nu_var), getuid(masterform), false) + id = VarId(MasterBendSecondStageCostVar, getid(nu_var), getuid(masterform)) ) end return @@ -385,7 +385,7 @@ function create_side_vars_constrs!( ub = getcurub(origform, var), kind = Continuous, is_explicit = true, - id = Id{Variable}(BendSpPosSlackFirstStageVar, varid, getuid(masterform), false) + id = VarId(BendSpPosSlackFirstStageVar, varid, getuid(masterform)) ) name = string("μ⁻[", split(getname(origform, var), "[")[end], "]") diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 6543dd7f0..e9f2b1113 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -178,7 +178,6 @@ const CONSTRAINTDUAL_SINGLEVAR = String[ "solve_single_variable_dual_min", # TODO bug "linear14", # TODO bug "linear1", # TODO bug - "linear10" # TODO bug ] const UNCOVERED_TERMINATION_STATUS = [ diff --git a/test/custom_var_cuts_tests.jl b/test/custom_var_cuts_tests.jl index 4e4a70815..a76d30e1b 100644 --- a/test/custom_var_cuts_tests.jl +++ b/test/custom_var_cuts_tests.jl @@ -148,13 +148,7 @@ function custom_var_cuts_test() MOI.set(model, MOI.UserCutCallback(), custom_cut_sep) JuMP.optimize!(model) - @show JuMP.objective_value(model) @test JuMP.termination_status(model) == MOI.OPTIMAL - - dual_sol = Coluna.get_best_lp_dual_sol(JuMP.unsafe_backend(model).result) - for id in cut_ids - @show dual_sol[id] - end end end From 33fc55574e93aa8eee672d208ede1e9a47a436c5 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Thu, 2 Sep 2021 10:00:26 +0200 Subject: [PATCH 09/19] clean Project --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index 9e2777cef..688e37ecf 100644 --- a/Project.toml +++ b/Project.toml @@ -5,12 +5,10 @@ version = "0.4.0" [deps] BlockDecomposition = "6cde8614-403a-11e9-12f1-c10d0f0caca0" -ColunaDemos = "a54e61d4-7723-11e9-2469-af255fcaa246" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" DynamicSparseArrays = "8086fd22-9a0c-46a5-a6c8-6e24676501fe" -KnapsackLib = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" From a19d53937d931955336dbe96f3196190f4025d4f Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 3 Sep 2021 00:05:44 +0200 Subject: [PATCH 10/19] wip --- src/Algorithm/colgen.jl | 24 ++-- src/Algorithm/colgenstabilization.jl | 15 +- src/Algorithm/utilities/optimizationstate.jl | 4 +- src/ColunaBase/ColunaBase.jl | 4 +- src/ColunaBase/solsandbounds.jl | 54 +++---- src/MathProg/MOIinterface.jl | 14 +- src/MathProg/MathProg.jl | 2 +- src/MathProg/bounds.jl | 6 +- src/MathProg/formulation.jl | 4 +- src/MathProg/solutions.jl | 144 +++++++++++++++---- src/MathProg/types.jl | 10 +- test/unit/containers/solsandbounds.jl | 8 +- test/unit/optimizationstate.jl | 22 +-- 13 files changed, 204 insertions(+), 107 deletions(-) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index 1d762ef2c..dde6b7727 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -343,7 +343,7 @@ function solve_sp_to_gencol!( sense = getobjsense(masterform) bestsol = get_best_ip_primal_sol(sp_optstate) - if bestsol !== nothing && bestsol.status == FEASIBLE_SOL + if bestsol !== nothing && getstatus(bestsol) == FEASIBLE_SOL spinfo.bestsol = bestsol spinfo.isfeasible = true for sol in get_ip_primal_sols(sp_optstate) @@ -381,7 +381,7 @@ function updatereducedcosts!( reform::Reformulation, redcostshelper::ReducedCostsCalculationHelper, masterdualsol::DualSolution ) redcosts = Dict{VarId,Float64}() - result = redcostshelper.dwsprep_coefmatrix * getsol(masterdualsol) + result = redcostshelper.dwsprep_coefmatrix * masterdualsol.solution.sol for (i, varid) in enumerate(redcostshelper.dwspvarids) redcosts[varid] = redcostshelper.perencosts[i] - get(result, varid, 0.0) end @@ -525,7 +525,7 @@ function update_lagrangian_dual_bound!( end end - valid_lagr_bound = DualBound{S}(puremastvars_contrib + dualsol.bound) + valid_lagr_bound = DualBound{S}(puremastvars_contrib + getbound(dualsol)) for (spuid, spinfo) in spinfos valid_lagr_bound += spinfo.valid_dual_bound_contrib end @@ -534,7 +534,7 @@ function update_lagrangian_dual_bound!( update_lp_dual_bound!(optstate, valid_lagr_bound) if stabilization_is_used(algo) - pseudo_lagr_bound = DualBound{S}(puremastvars_contrib + dualsol.bound) + pseudo_lagr_bound = DualBound{S}(puremastvars_contrib + getbound(dualsol)) for (spuid, spinfo) in spinfos pseudo_lagr_bound += spinfo.pseudo_dual_bound_contrib end @@ -561,9 +561,9 @@ function compute_subgradient_contribution( end end - for (spuid, spinfo) in spinfos + for (_, spinfo) in spinfos iszero(spinfo.ub) && continue - mult = improving_red_cost(spinfo.bestsol.bound, algo, sense) ? spinfo.ub : spinfo.lb + mult = improving_red_cost(getbound(spinfo.bestsol), algo, sense) ? spinfo.ub : spinfo.lb for (sp_var_id, sp_var_val) in spinfo.bestsol for (master_constrid, sp_var_coef) in @view master_coef_matrix[:,sp_var_id] if !(getduty(master_constrid) <= MasterConvexityConstr) @@ -575,13 +575,16 @@ function compute_subgradient_contribution( end end - return DualSolution(master, constrids, constrvals, 0.0, UNKNOWN_SOLUTION_STATUS) + return DualSolution( + master, constrids, constrvals, VarId[], Float64[], ActiveBound[], 0.0, + UNKNOWN_SOLUTION_STATUS + ) end function move_convexity_constrs_dual_values!( spinfos::Dict{FormId,SubprobInfo}, dualsol::DualSolution ) - newbound = dualsol.bound + newbound = getbound(dualsol) for (spuid, spinfo) in spinfos spinfo.lb_dual = dualsol[spinfo.lb_constr_id] spinfo.ub_dual = dualsol[spinfo.ub_constr_id] @@ -597,7 +600,10 @@ function move_convexity_constrs_dual_values!( push!(values, value) end end - return DualSolution(dualsol.model, constrids, values, newbound, FEASIBLE_SOL) + return DualSolution( + getmodel(dualsol), constrids, values, VarId[], Float64[], ActiveBound[], newbound, + FEASIBLE_SOL + ) end function get_pure_master_vars(master::Formulation) diff --git a/src/Algorithm/colgenstabilization.jl b/src/Algorithm/colgenstabilization.jl index 90fe48f2a..c6ef0cabe 100644 --- a/src/Algorithm/colgenstabilization.jl +++ b/src/Algorithm/colgenstabilization.jl @@ -105,7 +105,10 @@ function linear_combination(in_dual_sol::DualSolution, out_dual_sol::DualSolutio for (i, constrid) in enumerate(constrids) bound += constrvals[i] * getcurrhs(form, constrid) end - return DualSolution(form, constrids, constrvals, bound, UNKNOWN_FEASIBILITY) + return DualSolution( + form, constrids, constrvals, VarId[], Float64[], ActiveBound[], bound, + UNKNOWN_FEASIBILITY + ) end function update_stab_after_rm_solve!( @@ -152,7 +155,10 @@ function update_alpha_automatically!( # first we calculate the in-sep direction constrids, constrvals = componentwisefunction(smooth_dual_sol, unit.stabcenter, -) - in_sep_direction = DualSolution(master, constrids, constrvals, 0.0, UNKNOWN_FEASIBILITY) + in_sep_direction = DualSolution( + master, constrids, constrvals, VarId[], Float64[], ActiveBound[], 0.0, + UNKNOWN_FEASIBILITY + ) in_sep_dir_norm = norm(in_sep_direction) # we initialize the subgradient with the right-hand-side of all master constraints @@ -166,7 +172,10 @@ function update_alpha_automatically!( push!(constrrhs, getcurrhs(master, constrid)) end end - subgradient = DualSolution(master, constrids, constrrhs, 0.0, UNKNOWN_FEASIBILITY) + subgradient = DualSolution( + master, constrids, constrrhs, VarId[], Float64[], ActiveBound[], 0.0, + UNKNOWN_FEASIBILITY + ) # we calculate the subgradient at the sep point for (constrid, value) in subgradient_contribution diff --git a/src/Algorithm/utilities/optimizationstate.jl b/src/Algorithm/utilities/optimizationstate.jl index 5fee80999..149d0d4d1 100644 --- a/src/Algorithm/utilities/optimizationstate.jl +++ b/src/Algorithm/utilities/optimizationstate.jl @@ -12,7 +12,7 @@ mutable struct OptimizationState{F<:AbstractFormulation,S<:Coluna.AbstractSense} lp_dual_sols::Vector{DualSolution{F}} end -function bestbound!(solutions::Vector{Sol}, max_len::Int, new_sol::Sol) where {Sol<:Solution} +function bestbound!(solutions::Vector{Sol}, max_len::Int, new_sol::Sol) where {Sol<:AbstractSolution} push!(solutions, new_sol) sort!(solutions, rev = true) while length(solutions) > max_len @@ -21,7 +21,7 @@ function bestbound!(solutions::Vector{Sol}, max_len::Int, new_sol::Sol) where {S return end -function set!(solutions::Vector{Sol}, ::Int, new_sol::Sol) where {Sol<:Solution} +function set!(solutions::Vector{Sol}, ::Int, new_sol::Sol) where {Sol<:AbstractSolution} empty!(solutions) push!(solutions, new_sol) return diff --git a/src/ColunaBase/ColunaBase.jl b/src/ColunaBase/ColunaBase.jl index 22f044dd1..38b08f18b 100644 --- a/src/ColunaBase/ColunaBase.jl +++ b/src/ColunaBase/ColunaBase.jl @@ -19,8 +19,8 @@ export AbstractModel, AbstractProblem, AbstractSense, AbstractMinSense, Abstract export NestedEnum, @nestedenum, @exported_nestedenum # solsandbounds.jl -export Bound, Solution, getvalue, isbetter, diff, gap, printbounds, getsol, - getstatus, remove_until_last_point +export Bound, Solution, getvalue, getbound, isbetter, diff, gap, printbounds, + getstatus, remove_until_last_point, getmodel # Statuses export TerminationStatus, SolutionStatus, OPTIMIZE_NOT_CALLED, OPTIMAL, diff --git a/src/ColunaBase/solsandbounds.jl b/src/ColunaBase/solsandbounds.jl index b5ceaa9d1..def2ef715 100644 --- a/src/ColunaBase/solsandbounds.jl +++ b/src/ColunaBase/solsandbounds.jl @@ -214,24 +214,24 @@ function convert_status(coluna_status::SolutionStatus) return MOI.OTHER_RESULT_STATUS end -# Solution -struct Solution{Model<:AbstractModel,Decision,Value,T} <: AbstractDict{Decision,Value} +# Basic structure of a solution +struct Solution{Model<:AbstractModel,Decision,Value} <: AbstractDict{Decision,Value} model::Model bound::Float64 status::SolutionStatus sol::DynamicSparseArrays.PackedMemoryArray{Decision,Value} - custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData} - supp_data::T end """ +Solution is an internal data structure of Coluna and should not be used in +algorithms. See `MathProg.PrimalSolution` & `MathProg.DualSolution` instead. + Solution( model::AbstractModel, decisions::Vector, values::Vector, - solution_values::Float64, - status::SolutionStatus, - [custom_data]::Union{Nothing, BlockDecomposition.AbstractCustomData} + solution_value::Float64, + status::SolutionStatus ) Create a solution to the `model`. Other arguments are: @@ -240,48 +240,38 @@ Create a solution to the `model`. Other arguments are: - `solution_value` is the value of the solution. - `status` is the solution status. """ -function Solution{Mo,De,Va,T}( +function Solution{Mo,De,Va}( model::Mo, decisions::Vector{De}, values::Vector{Va}, solution_value::Float64, - status::SolutionStatus, custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData}, - supp_data::T + status::SolutionStatus ) where {Mo<:AbstractModel,De,Va,T} sol = DynamicSparseArrays.dynamicsparsevec(decisions, values) - return Solution(model, solution_value, status, sol, custom_data, supp_data) + return Solution(model, solution_value, status, sol) end -""" - getsol(solution) - -Return the dynamic sparse vector that describes `solution`. -""" -getsol(s::Solution) = s.sol +"Return the model of a solution." +getmodel(s::Solution) = s.model -""" - getvalue(solution) -> Float64 +"Return the value (as a Bound) of `solution`" +getbound(s::Solution) = s.bound -Return the value of `solution`. -""" +"Return the value of `solution`." getvalue(s::Solution) = float(s.bound) -""" - getstatus(solution) -> SolutionStatus - -Return the solution status of `solution`. -""" +"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) Base.length(s::Solution) = length(s.sol) -Base.get(s::Solution{Mo,De,Va,T}, id::De, default) where {Mo,De,Va,T} = s.sol[id] # TODO : REMOVE -Base.getindex(s::Solution{Mo,De,Va,T}, id::De) where {Mo,De,Va,T} = Base.getindex(s.sol, id) -Base.setindex!(s::Solution{Mo,De,Va,T}, val::Va, id::De) where {Mo,De,Va,T} = s.sol[id] = val +#Base.get(s::Solution{Mo,De,Va}, id::De, default) where {Mo,De,Va} = s.sol[id] # TODO : REMOVE +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 function Base.filter(f::Function, s::S) where {S <: Solution} - return S(s.model, s.bound, s.status, filter(f, s.sol), s.custom_data, s.supp_data) + return S(s.model, s.bound, s.status, filter(f, s.sol)) end -function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va,T}, valcmp=(==)) where {Mo,De,Va,T} +function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va}, valcmp=(==)) where {Mo,De,Va} v = get(a, p[1], Base.secret_table_token) if v !== Base.secret_table_token return valcmp(v, p[2]) @@ -289,7 +279,7 @@ function Base.in(p::Tuple{De,Va}, a::Solution{Mo,De,Va,T}, valcmp=(==)) where {M return false end -function Base.show(io::IO, solution::Solution{Mo,De,Va,T}) where {Mo,De,Va,T} +function Base.show(io::IO, solution::Solution{Mo,De,Va}) where {Mo,De,Va} println(io, "Solution") for (decision, value) in solution println(io, "| ", decision, " = ", value) diff --git a/src/MathProg/MOIinterface.jl b/src/MathProg/MOIinterface.jl index b8e3bb28a..a571b45a0 100644 --- a/src/MathProg/MOIinterface.jl +++ b/src/MathProg/MOIinterface.jl @@ -291,6 +291,9 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul # Get dual value & active bound of variables var_red_costs = Dict{VarId, Tuple{Float64,ActiveBound}}() + varids = VarId[] + varvals = Float64[] + activebounds = ActiveBound[] for (varid, var) in getvars(form) moi_bounds_index = getbounds(getmoirecord(var)) MOI.is_valid(inner, moi_bounds_index) || continue @@ -302,12 +305,16 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul if basis_status == MOI.NONBASIC_AT_LOWER solcost += val * getcurlb(form, varid) if abs(val) > Coluna.TOL - var_red_costs[varid] = (val, LOWER) + push!(varids, varid) + push!(varvals, val) + push!(activebounds, LOWER) end elseif basis_status == MOI.NONBASIC_AT_UPPER solcost += val * getcurub(form, varid) if abs(val) > Coluna.TOL - var_red_costs[varid] = (val, UPPER) + push!(varids, varid) + push!(varvals, val) + push!(activebounds, UPPER) end elseif abs(val) > Coluna.TOL @warn """ @@ -319,7 +326,8 @@ function get_dual_solutions(form::F, optimizer::MoiOptimizer) where {F <: Formul sense = getobjsense(form) == MinSense ? 1.0 : -1.0 push!(solutions, DualSolution( - form, solconstrs, solvals, sense*solcost, FEASIBLE_SOL; var_red_costs = var_red_costs + form, solconstrs, solvals, varids, varvals, activebounds, sense*solcost, + FEASIBLE_SOL )) end return solutions diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index b5d3a0ddf..39aa0c1d5 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -100,7 +100,7 @@ export Variable, Constraint, VarId, ConstrId, VarMembership, ConstrMembership, S isexplicit, getname, getbranchingpriority, reset!, getreducedcost, setperenkind!, setsinglevarconstr! # Types & methods related to solutions & bounds -export PrimalBound, DualBound, PrimalSolution, DualSolution, ObjValues +export PrimalBound, DualBound, AbstractSolution, PrimalSolution, DualSolution, ActiveBound, ObjValues # Methods related to projections export projection_is_possible, proj_cols_on_rep diff --git a/src/MathProg/bounds.jl b/src/MathProg/bounds.jl index 453ad8d46..64fdba700 100644 --- a/src/MathProg/bounds.jl +++ b/src/MathProg/bounds.jl @@ -103,7 +103,7 @@ _lp_gap(ov::ObjValues) = gap(ov.lp_primal_bound, ov.lp_dual_bound) function _ip_gap_closed( ov::ObjValues; atol = Coluna.DEF_OPTIMALITY_ATOL, rtol = Coluna.DEF_OPTIMALITY_RTOL ) - return (_ip_gap(ov) <= 0) || _gap_closed( + return _ip_gap(ov) <= 0 || _gap_closed( ov.ip_primal_bound.value, ov.ip_dual_bound.value, atol = atol, rtol = rtol ) end @@ -111,7 +111,7 @@ end function _lp_gap_closed( ov::ObjValues; atol = Coluna.DEF_OPTIMALITY_ATOL, rtol = Coluna.DEF_OPTIMALITY_RTOL ) - return (_lp_gap(ov) <= 0) || _gap_closed( + return _lp_gap(ov) <= 0 || _gap_closed( ov.lp_primal_bound.value, ov.lp_dual_bound.value, atol = atol, rtol = rtol ) end @@ -120,7 +120,7 @@ function _gap_closed( x::Number, y::Number; atol::Real = 0, rtol::Real = atol > 0 ? 0 : √eps, norm::Function = abs ) - return (x == y) || (isfinite(x) && isfinite(y) && norm(x - y) <= max(atol, rtol*min(norm(x), norm(y)))) + return x == y || (isfinite(x) && isfinite(y) && norm(x - y) <= max(atol, rtol*min(norm(x), norm(y)))) end ## Bound updates diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index d6fcb171c..075189e6e 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -263,7 +263,7 @@ function setprimalsol!(form::Formulation, new_primal_sol::PrimalSolution)::Tuple # look for an identical column for (cur_sol_id, cur_cost) in primal_sol_costs cur_primal_sol = primal_sols[:, cur_sol_id] - if isapprox(new_cost, cur_cost) && getsol(new_primal_sol) == cur_primal_sol + if isapprox(new_cost, cur_cost) && new_primal_sol == cur_primal_sol return (false, cur_sol_id) end end @@ -313,7 +313,7 @@ function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool, is_identical = true cur_dual_sol = @view dual_sols[:,cur_sol_id] for (constr_id, constr_val) in cur_dual_sol - if factor * getsol(new_dual_sol)[constr_id] != constr_val + if factor * new_dual_sol.solution.sol[constr_id] != constr_val is_identical = false break end diff --git a/src/MathProg/solutions.jl b/src/MathProg/solutions.jl index 641e4aac2..582b4547d 100644 --- a/src/MathProg/solutions.jl +++ b/src/MathProg/solutions.jl @@ -1,27 +1,104 @@ -# new structures for solutions +# MathProg > Solutions +# Representations of the primal & dual solutions to a MILP formulation -# Constructors for Primal & Dual Solutions -const PrimalSolution{M} = Solution{M, VarId, Float64, Nothing} +abstract type AbstractSolution end -@enum ActiveBound LOWER UPPER -const DualSolution{M} = Solution{M, ConstrId, Float64, Dict{VarId, Tuple{Float64, ActiveBound}}} +# Primal Solution +struct PrimalSolution{M} <: AbstractSolution + solution::Solution{M,VarId,Float64} + custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData} +end + +""" + PrimalSolution( + form::AbstractFormulation, + varids::Vector{VarId}, + varvals::Vector{Float64}, + cost::Float64, + status::SolutionStatus; + custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData} = nothing + ) + +Create a primal solution to the formulation `form` of cost `cost` and status `status`. +The representations of the soslution is `varids` the set of the ids of the variables +and `varvals` the values of the variables (`varvals[i]` is value of variable `varids[i]`). +The user can also attach to the primal solution a customized representation +`custom_data`. +""" function PrimalSolution( - form::M, decisions, vals, cost, status, custom_data = nothing + form::M, varids, varvals, cost, status; custom_data = nothing ) where {M<:AbstractFormulation} - return PrimalSolution{M}(form, decisions, vals, cost, status, custom_data, nothing) + @assert length(varids) == length(varvals) + sol = Solution{M,VarId,Float64}(form, varids, varvals, cost, status) + return PrimalSolution{M}(sol, custom_data) +end + +# Dual Solution + +# Indicate whether the active bound of a variable is the lower or the upper one. +@enum ActiveBound LOWER UPPER + +struct DualSolution{M} <: AbstractSolution + solution::Solution{M,ConstrId,Float64} + var_redcosts::Dict{VarId, Tuple{Float64,ActiveBound}} + custom_data::Union{Nothing, BlockDecomposition.AbstractCustomData} end +""" + DualSolution( + form::AbstractFormulation, + constrids::Vector{ConstrId}, + constrvals::Vector{Float64}, + varids::Vector{VarId}, + varvals::Vector{Float64}, + varactivebounds::Vector{ActiveBound}, + cost::Float64, + status::SolutionStatus; + custom_data::Union{Nothing, BlockDecomposition.AbstractColumnData} = nothing + ) + +Create a dual solution to the formulation `form` of cost `cost` and status `status`. +The first representation of the dual solution is mandatory. +It contains `constrids` the set of ids of the constraints and `constrvals` the values +of the constraints (`constrvals[i]` is dual value of `constrids[i]`). +It also contains `varvals[i]` the dual values of the bound constraint `varactivebounds[i]` of the variables `varids`. + +The user can also attach to the dual solution a customized representation +`custom_data`. +""" function DualSolution( - form::M, decisions, vals, cost, status, custom_data = nothing; - var_red_costs = Dict{VarId, Tuple{Float64, ActiveBound}}(), + form::M, constrids, constrvals, varids, varvals, varactivebounds, cost, status; + custom_data = nothing ) where {M<:AbstractFormulation} - return DualSolution{M}(form, decisions, vals, cost, status, custom_data, var_red_costs) + @assert length(constrids) == length(constrvals) + @assert length(varids) == length(varvals) == length(varactivebounds) + var_redcosts = Dict{VarId, Tuple{Float64,ActiveBound}}() + for i in 1:length(varids) + var_redcosts[varids[i]] = (varvals[i],varactivebounds[i]) + end + sol = Solution{M,ConstrId,Float64}(form, constrids, constrvals, cost, status) + return DualSolution{M}(sol, var_redcosts, custom_data) end -function Base.isinteger(sol::Solution) +# Redefine methods from ColunaBase to access the formulation, the value, the +# status of a Solution, and other specific information +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.getindex(s::AbstractSolution, id) = Base.getindex(s.solution, id) +Base.setindex!(s::AbstractSolution, val, id) = Base.setindex!(s.solution, val, id) + +# Iterating over a PrimalSolution or a DualSolution is similar to iterating over +# ColunaBase.Solution +Base.iterate(s::AbstractSolution) = iterate(s.solution) +Base.iterate(s::AbstractSolution, state) = iterate(s.solution, state) + +function Base.isinteger(sol::PrimalSolution) for (vc_id, val) in sol - if getperenkind(sol.model, vc_id) !== Continuous && abs(round(val) - val) > 1e-5 + if getperenkind(getmodel(sol), vc_id) !== Continuous && abs(round(val) - val) > 1e-5 return false end end @@ -29,48 +106,63 @@ function Base.isinteger(sol::Solution) end function Base.isless(s1::PrimalSolution, s2::PrimalSolution) - getobjsense(s1.model) == MinSense && return s1.bound > s2.bound - return s1.bound < s2.bound + getobjsense(getmodel(s1)) == MinSense && return s1.solution.bound > s2.solution.bound + return s1.solution.bound < s2.solution.bound end function Base.isless(s1::DualSolution, s2::DualSolution) - getobjsense(s1.model) == MinSense && return s1.bound < s2.bound - return s1.bound > s2.bound + getobjsense(getmodel(s1)) == MinSense && return s1.solution.bound < s2.solution.bound + return s1.solution.bound > s2.solution.bound end function contains(sol::PrimalSolution, f::Function) - for (varid, val) in sol + for (varid, _) in sol f(varid) && return true end return false end function contains(sol::DualSolution, f::Function) - for (constrid, val) in sol + for (constrid, _) in sol f(constrid) && return true end return false end -function _assert_same_model(sols::NTuple{N, Solution{M, I, Float64,T}}) where {N,M,I,T} +function _assert_same_model(sols::NTuple{N, S}) where {N,S<:AbstractSolution} for i in 2:length(sols) - sols[i-1].model != sols[i].model && return false + getmodel(sols[i-1]) != getmodel(sols[i]) && return false end return true end -function Base.cat(sols::Solution{M, I, Float64, T}...) where {M,I,T} +function Base.cat(sols::PrimalSolution...) _assert_same_model(sols) || error("Cannot concatenate solutions not attached to the same model.") + ids = VarId[] + vals = Float64[] + for sol in sols, (id, value) in sol + push!(ids, id) + push!(vals, value) + end + return PrimalSolution( + getmodel(sols[1]), ids, vals, sum(getvalue.(sols)), getstatus(sols[1]) + ) +end - ids = I[] +function Base.cat(sols::DualSolution...) + _assert_same_model(sols) || error("Cannot concatenate solutions not attached to the same model.") + ids = ConstrId[] vals = Float64[] for sol in sols, (id, value) in sol push!(ids, id) push!(vals, value) end - return Solution{M,I,Float64,T}( - sols[1].model, ids, vals, sum(getvalue.(sols)), sols[1].status, nothing, T() + # TODO : varids, varvals, activebounds + + return DualSolution( + getmodel(sols[1]), ids, VarId[], Float64[], ActiveBound[], vals, + sum(getvalue.(sols)), getstatus(sols[1]) ) end @@ -85,7 +177,7 @@ end function Base.show(io::IO, solution::DualSolution{M}) where {M} println(io, "Dual solution") for (constrid, value) in solution - println(io, "| ", getname(solution.model, constrid), " = ", value) + println(io, "| ", getname(getmodel(solution), constrid), " = ", value) end Printf.@printf(io, "└ value = %.2f \n", getvalue(solution)) end @@ -93,7 +185,7 @@ end function Base.show(io::IO, solution::PrimalSolution{M}) where {M} println(io, "Primal solution") for (varid, value) in solution - println(io, "| ", getname(solution.model, varid), " = ", value) + println(io, "| ", getname(getmodel(solution), varid), " = ", value) end Printf.@printf(io, "└ value = %.2f \n", getvalue(solution)) end diff --git a/src/MathProg/types.jl b/src/MathProg/types.jl index 7768225cd..48f1a72e8 100644 --- a/src/MathProg/types.jl +++ b/src/MathProg/types.jl @@ -1,8 +1,4 @@ abstract type AbstractVarConstr end -abstract type AbstractVarConstrId end -abstract type AbstractState end -abstract type AstractMoiDef end -abstract type AbstractMembership end abstract type AbstractVcData end abstract type AbstractOptimizer end @@ -13,9 +9,7 @@ struct Dual <: Coluna.AbstractDualSpace end struct MinSense <: Coluna.AbstractMinSense end struct MaxSense <: Coluna.AbstractMaxSense end -# # Duties for variables and constraints -# """ Duty{Variable} @@ -34,7 +28,7 @@ If a duty `Duty1` inherits from `Duty2`, then ```jldoctest julia> Duty1 <= Duty2 true -```` +``` """ struct Duty{VC <: AbstractVarConstr} <: NestedEnum value::UInt @@ -56,8 +50,6 @@ end # TODO remove following exported_enum @exported_enum FormulationPhase HybridPhase PurePhase1 PurePhase2 # TODO : remove from Benders -#@exported_enum VcSelectionCriteria Static Dynamic Delayed Artificial Implicit Explicit # Not used -#@exported_enum SolutionMethod DirectMip DantzigWolfeDecomposition BendersDecomposition # Not used const FormId = Int8 diff --git a/test/unit/containers/solsandbounds.jl b/test/unit/containers/solsandbounds.jl index 723cbe949..f6697affd 100644 --- a/test/unit/containers/solsandbounds.jl +++ b/test/unit/containers/solsandbounds.jl @@ -318,8 +318,8 @@ function solution_unit() primalsol2 = PrimalSolution(form, [getid(var)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY) @test isless(primalsol1, primalsol2) # primalsol1 is worse than primalsol2 for min sense - dualsol1 = DualSolution(form, [getid(constr)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY) - dualsol2 = DualSolution(form, [getid(constr)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY) + dualsol1 = DualSolution(form, [getid(constr)], [1.0], VarId[], Float64[], ActiveBound[], 1.0, CB.UNKNOWN_FEASIBILITY) + dualsol2 = DualSolution(form, [getid(constr)], [0.0], VarId[], Float64[], ActiveBound[], 0.0, CB.UNKNOWN_FEASIBILITY) @test isless(dualsol2, dualsol1) # dualsol2 is worse than dualsol1 for min sense # MaxSense @@ -333,8 +333,8 @@ function solution_unit() primalsol2 = PrimalSolution(form, [getid(var)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY) @test isless(primalsol2, primalsol1) # primalsol2 is worse than primalsol1 for max sense - dualsol1 = DualSolution(form, [getid(constr)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY) - dualsol2 = DualSolution(form, [getid(constr)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY) + dualsol1 = DualSolution(form, [getid(constr)], [1.0], VarId[], Float64[], ActiveBound[], 1.0, CB.UNKNOWN_FEASIBILITY) + dualsol2 = DualSolution(form, [getid(constr)], [0.0], VarId[], Float64[], ActiveBound[], 0.0, CB.UNKNOWN_FEASIBILITY) @test isless(dualsol1, dualsol2) # dualsol1 is worse than dualsol2 for max sense end return diff --git a/test/unit/optimizationstate.jl b/test/unit/optimizationstate.jl index 253a0f9d6..c86ba91db 100644 --- a/test/unit/optimizationstate.jl +++ b/test/unit/optimizationstate.jl @@ -16,7 +16,7 @@ function update_sol_tests() max_length_lp_primal_sols = 2.0, max_length_lp_dual_sols = 2.0 ) primalsol = PrimalSolution(form, [getid(var)], [2.0], 2.0, CB.UNKNOWN_FEASIBILITY) - dualsol = DualSolution(form, [getid(constr)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY) + dualsol = DualSolution(form, [getid(constr)], [1.0], VarId[], Float64[], ActiveBound[], 1.0, CB.UNKNOWN_FEASIBILITY) ### ip primal update_ip_primal_sol!(state, primalsol) @@ -76,7 +76,7 @@ function update_sol_tests() max_length_lp_primal_sols = 2.0, max_length_lp_dual_sols = 2.0 ) primalsol = PrimalSolution(form, [getid(var)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY) - dualsol = DualSolution(form, [getid(constr)], [2.0], 2.0, CB.UNKNOWN_FEASIBILITY) + dualsol = DualSolution(form, [getid(constr)], [2.0], VarId[], Float64[], ActiveBound[], 2.0, CB.UNKNOWN_FEASIBILITY) ### ip primal update_ip_primal_sol!(state, primalsol) @@ -116,7 +116,7 @@ function update_sol_tests() # check that incumbent bound is updated @test get_lp_dual_bound(state) == 2.0 update_lp_dual_sol!(state, DualSolution( - form, [getid(constr)], [3.0], 3.0, CB.UNKNOWN_FEASIBILITY + form, [getid(constr)], [3.0], VarId[], Float64[], ActiveBound[], 3.0, CB.UNKNOWN_FEASIBILITY )) # check that solution worse than `dualsol` is NOT added to `state.lp_dual_sols` @test length(get_lp_dual_sols(state)) == 1 @@ -133,7 +133,7 @@ function add_sol_tests() constr = ClMP.setconstr!(form, "constr1", ClMP.OriginalConstr) state = OptimizationState(form) primalsol = PrimalSolution(form, [getid(var)], [2.0], 2.0, CB.UNKNOWN_FEASIBILITY) - dualsol = DualSolution(form, [getid(constr)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY) + dualsol = DualSolution(form, [getid(constr)], [1.0], VarId[], Float64[], ActiveBound[], 1.0, CB.UNKNOWN_FEASIBILITY) ### ip primal add_ip_primal_sols!( @@ -164,7 +164,7 @@ function add_sol_tests() ### lp dual add_lp_dual_sol!(state, DualSolution( - form, [getid(constr)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY + form, [getid(constr)], [0.0], VarId[], Float64[], ActiveBound[], 0.0, CB.UNKNOWN_FEASIBILITY )) # check that incumbent bound is updated @test get_lp_dual_bound(state) == 0.0 @@ -186,7 +186,7 @@ function add_sol_tests() constr = ClMP.setconstr!(form, "constr1", ClMP.OriginalConstr) state = OptimizationState(form) primalsol = PrimalSolution(form, [getid(var)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY) - dualsol = DualSolution(form, [getid(constr)], [2.0], 2.0, CB.UNKNOWN_FEASIBILITY) + dualsol = DualSolution(form, [getid(constr)], [2.0], VarId[], Float64[], ActiveBound[], 2.0, CB.UNKNOWN_FEASIBILITY) ### ip primal add_ip_primal_sols!( @@ -217,7 +217,7 @@ function add_sol_tests() ### lp dual add_lp_dual_sol!(state, DualSolution( - form, [getid(constr)], [3.0], 3.0, CB.UNKNOWN_FEASIBILITY + form, [getid(constr)], [3.0], VarId[], Float64[], ActiveBound[], 3.0, CB.UNKNOWN_FEASIBILITY )) # check that incumbent bound is updated @test get_lp_dual_bound(state) == 3.0 @@ -241,7 +241,7 @@ function set_sol_tests() form, ip_primal_bound = 3.0, lp_primal_bound = 3.0, lp_dual_bound = -1.0 ) primalsol = PrimalSolution(form, [getid(var)], [2.0], 2.0, CB.UNKNOWN_FEASIBILITY) - dualsol = DualSolution(form, [getid(constr)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY) + dualsol = DualSolution(form, [getid(constr)], [0.0], VarId[], Float64[], ActiveBound[], 0.0, CB.UNKNOWN_FEASIBILITY) ### ip primal set_ip_primal_sol!(state, PrimalSolution( @@ -269,7 +269,7 @@ function set_sol_tests() ### lp dual set_lp_dual_sol!(state, DualSolution( - form, [getid(constr)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY + form, [getid(constr)], [1.0], VarId[], Float64[], ActiveBound[], 1.0, CB.UNKNOWN_FEASIBILITY )) set_lp_dual_sol!(state, dualsol) # check that only the solution which was set last is in `state.lp_dual_sols` @@ -291,7 +291,7 @@ function set_sol_tests() form, ip_primal_bound = -1.0, lp_primal_bound = -1.0, lp_dual_bound = 3.0 ) primalsol = PrimalSolution(form, [getid(var)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY) - dualsol = DualSolution(form, [getid(constr)], [2.0], 2.0, CB.UNKNOWN_FEASIBILITY) + dualsol = DualSolution(form, [getid(constr)], [2.0], VarId[], Float64[], ActiveBound[], 2.0, CB.UNKNOWN_FEASIBILITY) ### ip primal set_ip_primal_sol!(state, PrimalSolution( @@ -319,7 +319,7 @@ function set_sol_tests() ### lp dual set_lp_dual_sol!(state, DualSolution( - form, [getid(constr)], [1.0], 1.0, CB.UNKNOWN_FEASIBILITY + form, [getid(constr)], [1.0], VarId[], Float64[], ActiveBound[], 1.0, CB.UNKNOWN_FEASIBILITY )) set_lp_dual_sol!(state, dualsol) # check that only the solution which was set last is in `state.lp_dual_sols` From b4f1aaac6ea7f557da299ab2ecc1259464447fa6 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 3 Sep 2021 15:51:49 +0200 Subject: [PATCH 11/19] v2 --- src/Algorithm/colgenstabilization.jl | 6 ++-- src/ColunaBase/solsandbounds.jl | 3 +- src/MOIcallbacks.jl | 5 ++- src/MOIwrapper.jl | 18 +++-------- src/MathProg/MOIinterface.jl | 3 +- src/MathProg/MathProg.jl | 5 +-- src/MathProg/buffer.jl | 25 ++++++++------- src/MathProg/clone.jl | 2 +- src/MathProg/constraint.jl | 27 +++++++++------- src/MathProg/duties.jl | 8 ----- src/MathProg/formulation.jl | 46 ++++++++++++++++----------- src/MathProg/manager.jl | 17 ++++++---- src/MathProg/solutions.jl | 17 +++++++++- src/MathProg/varconstr.jl | 20 ++++-------- src/MathProg/variable.jl | 4 +-- src/MathProg/vcids.jl | 46 +++++++++++++-------------- test/unit/containers/solsandbounds.jl | 6 ++-- test/unit/optimizationstate.jl | 2 +- test/unit/variable.jl | 2 +- 19 files changed, 139 insertions(+), 123 deletions(-) diff --git a/src/Algorithm/colgenstabilization.jl b/src/Algorithm/colgenstabilization.jl index c6ef0cabe..61f63c287 100644 --- a/src/Algorithm/colgenstabilization.jl +++ b/src/Algorithm/colgenstabilization.jl @@ -49,7 +49,7 @@ function init_stab_before_colgen_loop!(unit::ColGenStabilizationUnit) end function componentwisefunction(in_dual_sol::DualSolution, out_dual_sol::DualSolution, f::Function) - form = out_dual_sol.model + form = getmodel(out_dual_sol) constrids = Vector{ConstrId}() constrvals = Vector{Float64}() value::Float64 = 0.0 @@ -100,7 +100,7 @@ function linear_combination(in_dual_sol::DualSolution, out_dual_sol::DualSolutio (x, y) -> coeff * x + (1.0 - coeff) * y ) - form = in_dual_sol.model + form = getmodel(in_dual_sol) bound = 0.0 for (i, constrid) in enumerate(constrids) bound += constrvals[i] * getcurrhs(form, constrid) @@ -151,7 +151,7 @@ function update_alpha_automatically!( smooth_dual_sol::DualSolution{M}, subgradient_contribution::DualSolution{M} ) where {M} - master = lp_dual_sol.model + master = getmodel(lp_dual_sol) # first we calculate the in-sep direction constrids, constrvals = componentwisefunction(smooth_dual_sol, unit.stabcenter, -) diff --git a/src/ColunaBase/solsandbounds.jl b/src/ColunaBase/solsandbounds.jl index def2ef715..ee99aa012 100644 --- a/src/ColunaBase/solsandbounds.jl +++ b/src/ColunaBase/solsandbounds.jl @@ -263,10 +263,11 @@ getstatus(s::Solution) = s.status Base.iterate(s::Solution) = iterate(s.sol) Base.iterate(s::Solution, state) = iterate(s.sol, state) Base.length(s::Solution) = length(s.sol) -#Base.get(s::Solution{Mo,De,Va}, id::De, default) where {Mo,De,Va} = s.sol[id] # TODO : REMOVE +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 +# 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)) end diff --git a/src/MOIcallbacks.jl b/src/MOIcallbacks.jl index b2151f1cf..55acf4bb6 100644 --- a/src/MOIcallbacks.jl +++ b/src/MOIcallbacks.jl @@ -34,7 +34,10 @@ function MOI.submit( push!(values, 1.0) solval += getcurcost(form, setup_var_id) - sol = PrimalSolution(form, colunavarids, values, solval, FEASIBLE_SOL, custom_data) + sol = PrimalSolution( + form, colunavarids, values, solval, FEASIBLE_SOL; + custom_data = custom_data + ) push!(cb.callback_data.primal_solutions, sol) return end diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 10529baa8..60520ddf1 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -23,7 +23,7 @@ struct BoundConstraints eq::Union{Nothing,SingleVarConstraint} end -setname!(bc, set_type, name) = nothing +setname!(bc, set_type, name) = nothing # Fallback setname!(bc, ::Type{<:MOI.ZeroOne}, name) = bc.lower.name = bc.upper.name = name setname!(bc, ::Type{<:MOI.GreaterThan}, name) = bc.lower.name = name setname!(bc, ::Type{<:MOI.LessThan}, name) = bc.upper.name = name @@ -45,15 +45,11 @@ mutable struct Optimizer <: MOI.AbstractOptimizer inner::Problem objective_type::ObjectiveType annotations::Annotations - #varmap::Dict{MOI.VariableIndex,VarId} # For the user to get VariablePrimal vars::CleverDicts.CleverDict{MOI.VariableIndex, Variable} - #varids::CleverDicts.CleverDict{MOI.VariableIndex, VarId} moi_varids::Dict{VarId, MOI.VariableIndex} names_to_vars::Dict{String, MOI.VariableIndex} constrs::Dict{MOI.ConstraintIndex, Constraint} constrs_on_single_var::Dict{MOI.ConstraintIndex, BoundConstraints} - # constrs_on_single_var_to_vars::Dict{MOI.ConstraintIndex, VarId} - # constrs_on_single_var_to_names::Dict{MOI.ConstraintIndex, String} names_to_constrs::Dict{String, MOI.ConstraintIndex} result::OptimizationState disagg_result::Union{Nothing, OptimizationState} @@ -67,13 +63,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer model.inner = Problem(model.env) model.annotations = Annotations() model.vars = CleverDicts.CleverDict{MOI.VariableIndex, Variable}() - #model.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() # TODO : check if necessary to have two dicts for variables model.moi_varids = Dict{VarId, MOI.VariableIndex}() model.names_to_vars = Dict{String, MOI.VariableIndex}() model.constrs = Dict{MOI.ConstraintIndex, Constraint}() model.constrs_on_single_var = Dict{MOI.ConstraintIndex, BoundConstraints}() - # model.constrs_on_single_var_to_vars = Dict{MOI.ConstraintIndex, VarId}() - # model.constrs_on_single_var_to_names = Dict{MOI.ConstraintIndex, String}() model.names_to_constrs = Dict{String, MOI.ConstraintIndex}() model.result = OptimizationState(get_optimization_target(model.inner)) model.disagg_result = nothing @@ -322,9 +315,6 @@ function MOI.delete( coefmatrix[constrid, varid] = 0.0 end delete!(orig_form, constrid) -# ======= -# delete!(get_original_formulation(model.inner), getid(model.constrs[ci])) -# >>>>>>> master delete!(model.constrs, ci) return end @@ -966,7 +956,7 @@ function MOI.get( end function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.GreaterThan}) - value, activebound = get(dualsol.supp_data, bc.varid, (0.0, MathProg.LOWER)) + value, activebound = get(get_var_redcosts(dualsol), bc.varid, (0.0, MathProg.LOWER)) if value != 0.0 && activebound != MathProg.LOWER return 0.0 end @@ -974,7 +964,7 @@ function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.GreaterThan}) end function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.LessThan}) - value, activebound = get(dualsol.supp_data, bc.varid, (0.0, MathProg.UPPER)) + value, activebound = get(get_var_redcosts(dualsol), bc.varid, (0.0, MathProg.UPPER)) if value != 0.0 && activebound != MathProg.UPPER return 0.0 end @@ -982,7 +972,7 @@ function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.LessThan}) end function _singlevarconstrdualval(bc, dualsol, ::Type{<:MOI.EqualTo}) - value, _ = get(dualsol.supp_data, bc.varid, (0.0, MathProg.LOWER)) + value, _ = get(get_var_redcosts(dualsol), bc.varid, (0.0, MathProg.LOWER)) return value end diff --git a/src/MathProg/MOIinterface.jl b/src/MathProg/MOIinterface.jl index a571b45a0..9c0ba632b 100644 --- a/src/MathProg/MOIinterface.jl +++ b/src/MathProg/MOIinterface.jl @@ -154,8 +154,7 @@ function add_to_optimizer!( return end -function remove_from_optimizer!(form::Formulation, optimizer::MoiOptimizer, ids::Set{Id{T}}) where { - T <: AbstractVarConstr} +function remove_from_optimizer!(form::Formulation, optimizer::MoiOptimizer, ids::Set{I}) where {I<:Id} for id in ids vc = getelem(form, id) @logmsg LogLevel(-3) string("Removing varconstr of name ", getname(form, vc)) diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index 39aa0c1d5..abfb78f2b 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -92,7 +92,7 @@ export Original, DwMaster, BendersMaster, DwSp, BendersSp export isanArtificialDuty, isaStaticDuty, isaDynamicDuty, isanOriginalRepresentatives # Types and methods related to variables and constraints -export Variable, Constraint, VarId, ConstrId, VarMembership, ConstrMembership, SingleVarConstraint, +export Variable, Constraint, VarId, ConstrId, SingleVarConstrId, VarMembership, ConstrMembership, SingleVarConstraint, getperencost, setperencost!, getcurcost, setcurcost!, getperenlb, getcurlb, setcurlb!, getperenub, getcurub, setcurub!, getperenrhs, setperenrhs!, getcurrhs, setcurrhs!, getperensense, setperensense!, getcursense, setcursense!, getperenkind, getcurkind, setcurkind!, getperenincval, @@ -100,7 +100,8 @@ export Variable, Constraint, VarId, ConstrId, VarMembership, ConstrMembership, S isexplicit, getname, getbranchingpriority, reset!, getreducedcost, setperenkind!, setsinglevarconstr! # Types & methods related to solutions & bounds -export PrimalBound, DualBound, AbstractSolution, PrimalSolution, DualSolution, ActiveBound, ObjValues +export PrimalBound, DualBound, AbstractSolution, PrimalSolution, DualSolution, ActiveBound, ObjValues, + get_var_redcosts # Methods related to projections export projection_is_possible, proj_cols_on_rep diff --git a/src/MathProg/buffer.jl b/src/MathProg/buffer.jl index 18ada93f5..e63cad974 100644 --- a/src/MathProg/buffer.jl +++ b/src/MathProg/buffer.jl @@ -1,15 +1,15 @@ """ -A `VarConstrBuffer{T}` stores the ids of the entities of type `T` that will be -added and removed from a formulation. +A `VarConstrBuffer{I}` stores the ids of type `I` of the variables, constraints or +single variable constraints that will be added and removed from a formulation. """ -mutable struct VarConstrBuffer{T<:AbstractVarConstr} - added::Set{Id{T}} - removed::Set{Id{T}} +mutable struct VarConstrBuffer{I<:Id} + added::Set{I} + removed::Set{I} end -VarConstrBuffer{T}() where {T<:AbstractVarConstr} = VarConstrBuffer{T}(Set{T}(), Set{T}()) +VarConstrBuffer{I}() where {I<:Id} = VarConstrBuffer{I}(Set{I}(), Set{I}()) -function add!(buffer::VarConstrBuffer{VC}, id::Id{VC}) where {VC<:AbstractVarConstr} +function add!(buffer::VarConstrBuffer{I}, id::I) where {I<:Id} if id ∉ buffer.removed push!(buffer.added, id) else @@ -18,7 +18,7 @@ function add!(buffer::VarConstrBuffer{VC}, id::Id{VC}) where {VC<:AbstractVarCon return end -function remove!(buffer::VarConstrBuffer{VC}, id::Id{VC}) where {VC<:AbstractVarConstr} +function remove!(buffer::VarConstrBuffer{I}, id::I) where {I<:Id} if id ∉ buffer.added push!(buffer.removed, id) else @@ -41,15 +41,16 @@ mutable struct FormulationBuffer changed_bound::Set{VarId} # bound of a variable changed_var_kind::Set{VarId} # kind of a variable changed_rhs::Set{ConstrId} # rhs and sense of a constraint - var_buffer::VarConstrBuffer{Variable} # variable added or removed - constr_buffer::VarConstrBuffer{Constraint} # constraint added or removed + var_buffer::VarConstrBuffer{VarId} # variable added or removed + constr_buffer::VarConstrBuffer{ConstrId} # constraint added or removed + singlevarconstr_buffer::VarConstrBuffer{SingleVarConstrId} # single var constraint added or removed reset_coeffs::Dict{Pair{ConstrId,VarId},Float64} # coefficient of the matrix changed end FormulationBuffer() = FormulationBuffer( false, false, Set{VarId}(), Set{VarId}(), Set{VarId}(), Set{ConstrId}(), - VarConstrBuffer{Variable}(), VarConstrBuffer{Constraint}(), - Dict{Pair{ConstrId,VarId},Float64}() + VarConstrBuffer{VarId}(), VarConstrBuffer{ConstrId}(), + VarConstrBuffer{SingleVarConstrId}(), Dict{Pair{ConstrId,VarId},Float64}() ) add!(b::FormulationBuffer, varid::VarId) = add!(b.var_buffer, varid) diff --git a/src/MathProg/clone.jl b/src/MathProg/clone.jl index d73611686..a21ce75a8 100644 --- a/src/MathProg/clone.jl +++ b/src/MathProg/clone.jl @@ -68,7 +68,7 @@ function clonesinglevarconstr!( destform, name, constr.varid, duty; rhs = rhs, kind = kind, sense = sense, inc_val = inc_val, is_active = is_active, - id = ConstrId(duty, getid(constr), getuid(assignedform)) + id = SingleVarConstrId(duty, getid(constr), getuid(assignedform)) ) end diff --git a/src/MathProg/constraint.jl b/src/MathProg/constraint.jl index d7b7cb8ed..1bf8722e0 100644 --- a/src/MathProg/constraint.jl +++ b/src/MathProg/constraint.jl @@ -27,11 +27,7 @@ ConstrData(cd::ConstrData) = ConstrData( cd.rhs, cd.kind, cd.sense, cd.inc_val, cd.is_active, cd.is_explicit ) -""" - MoiConstrRecord - -Structure to hold the pointers to the MOI representation of a Coluna Constraint. -""" +"Structure to hold the pointers to the MOI representation of a Coluna Constraint." mutable struct MoiConstrRecord index::MoiConstrIndex end @@ -42,12 +38,12 @@ getindex(record::MoiConstrRecord) = record.index setindex!(record::MoiConstrRecord, index::MoiConstrIndex) = record.index = index """ - Constraint - Representation of a constraint in Coluna. +Coefficients of variables involved in the constraints are stored in the coefficient matrix. +If the constraint involves only one variable, you should use a `SingleVarConstraint`. """ mutable struct Constraint <: AbstractVarConstr - id::Id{Constraint} + id::Id{Constraint,:usual} name::String perendata::ConstrData curdata::ConstrData @@ -56,7 +52,7 @@ mutable struct Constraint <: AbstractVarConstr custom_data::Union{Nothing, BD.AbstractCustomData} end -const ConstrId = Id{Constraint} +const ConstrId = Id{Constraint,:usual} function Constraint( id::ConstrId, name::String; @@ -69,20 +65,29 @@ function Constraint( ) end +""" +Representation of a single variable constraint in Coluna : lb <= 1*var <= ub. +For performance reasons, Coluna does not store these constraints in the coefficient matrix. +""" mutable struct SingleVarConstraint <: AbstractVarConstr - id::ConstrId + id::Id{Constraint,:single} name::String varid::VarId perendata::ConstrData curdata::ConstrData end +const SingleVarConstrId = Id{Constraint,:single} + function SingleVarConstraint( - id::ConstrId, varid::VarId, name::String; constr_data = ConstrData() + id::SingleVarConstrId, varid::VarId, name::String; constr_data = ConstrData() ) return SingleVarConstraint(id, name, varid, constr_data, ConstrData(constr_data)) end +""" +Constraints generator (cut callback). +""" mutable struct RobustConstraintsGenerator nb_generated::Int kind::ConstrKind diff --git a/src/MathProg/duties.jl b/src/MathProg/duties.jl index 2b87c361c..be2d217d1 100644 --- a/src/MathProg/duties.jl +++ b/src/MathProg/duties.jl @@ -1,6 +1,4 @@ -# # Duties for a Formulation -# abstract type AbstractFormDuty end abstract type AbstractMasterDuty <: AbstractFormDuty end abstract type AbstractSpDuty <: AbstractFormDuty end @@ -29,9 +27,7 @@ end BendersSp() = BendersSp(Dict{VarId, VarId}()) -# # Duties tree for a Variable -# @exported_nestedenum begin Duty{Variable} AbstractOriginalVar <= Duty{Variable} @@ -65,9 +61,7 @@ BendersSp() = BendersSp(Dict{VarId, VarId}()) BendSpPrimalSol <= AbstractBendSpVar end -# # Duties tree for a Constraint -# @exported_nestedenum begin Duty{Constraint} AbstractOriginalConstr <= Duty{Constraint} @@ -101,9 +95,7 @@ end BendSpDualSol <= AbstractBendSpConstr end -# # Methods to get extra information about duties -# function isaStaticDuty(duty::NestedEnum) return duty <= OriginalVar || #duty <= OriginalExpression || diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 075189e6e..3d8061978 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -66,17 +66,12 @@ getvar(form::Formulation, id::VarId) = get(form.manager.vars, id, nothing) """ getconstr(formulation, constrid) -> Constraint + getconstr(formulation, singlevarconstrid) -> Constraint Returns the constraint with given `constrid` that belongs to `formulation`. """ getconstr(form::Formulation, id::ConstrId) = get(form.manager.constrs, id, nothing) - -""" - getsinglevarconstr(formulation, constrid) -> Constraint - -Returns the single variable constraint with given `constrid` that belongs to `formulation`. -""" -getsinglevarconstr(form::Formulation, id::ConstrId) = get(form.manager.single_var_constrs, id, nothing) +getconstr(form::Formulation, id::SingleVarConstrId) = get(form.manager.single_var_constrs, id, nothing) """ getvars(formulation) -> Dict{VarId, Variable} @@ -97,12 +92,14 @@ getconstrs(form::Formulation) = form.manager.constrs Returns all single variable constraints in `formulation`. - getsinglevarconstrs(formulation, varid) -> Dict{ConstrId, SingleVarConstr} or nothing + getsinglevarconstrs(formulation, varid) -> Dict{ConstrId, SingleVarConstraint} or nothing Return all single variable constraints of a formulation that applies to a given variable. """ getsinglevarconstrs(form::Formulation) = form.manager.single_var_constrs -getsinglevarconstrs(form::Formulation, varid::VarId) = get(form.manager.single_var_constrs_per_var, varid, Dict{ConstrId, SingleVarConstraint}()) +getsinglevarconstrs(form::Formulation, varid::VarId) = get( + form.manager.single_var_constrs_per_var, varid, Dict{ConstrId, SingleVarConstraint}() +) "Returns objective constant of the formulation." getobjconst(form::Formulation) = form.manager.objective_constant @@ -139,15 +136,28 @@ end "Returns all the optimizers of a formulation." getoptimizers(form::Formulation) = form.optimizers +""" + getelem(form, varid) -> Variable + getelem(form, constrid) -> Constraint + getelem(form, singlevarconstrid) -> SingleVarConstraint +Return the element of formulation `form` that has a given id. +""" getelem(form::Formulation, id::VarId) = getvar(form, id) getelem(form::Formulation, id::ConstrId) = getconstr(form, id) +getelem(form::Formulation, id::SingleVarConstrId) = getconstr(form, id) + +generatevarid(duty::Duty{Variable}, form::Formulation) = + VarId(duty, form.var_counter += 1, getuid(form), -1) + +generatevarid(duty::Duty{Variable}, form::Formulation, custom_family_id::Int) = + VarId(duty, form.var_counter += 1, getuid(form), custom_family_id) -generatevarid(duty::Duty{Variable}, form::Formulation) = VarId(duty, form.var_counter += 1, getuid(form), -1) -generatevarid( - duty::Duty{Variable}, form::Formulation, custom_family_id::Int -) = VarId(duty, form.var_counter += 1, getuid(form), custom_family_id) -generateconstrid(duty::Duty{Constraint}, form::Formulation) = ConstrId(duty, form.constr_counter += 1, getuid(form), -1) +generateconstrid(duty::Duty{Constraint}, form::Formulation) = + ConstrId(duty, form.constr_counter += 1, getuid(form), -1) + +generatesinglevarconstrid(duty::Duty{Constraint}, form::Formulation) = + SingleVarConstrId(duty, form.constr_counter += 1, getuid(form), -1) getmaster(form::Formulation{<:AbstractSpDuty}) = form.parent_formulation getreformulation(form::Formulation{<:AbstractMasterDuty}) = form.parent_formulation @@ -285,7 +295,7 @@ function _adddualsol!(form::Formulation, dualsol::DualSolution, dualsol_id::Cons form.manager.dual_sols[constrid, dualsol_id] = constrval end end - for (varid, varval) in dualsol.supp_data + for (varid, varval) in get_var_redcosts(dualsol) redcost, activebound = varval bound = activebound == LOWER ? getcurlb(form, varid) : getcurub(form, varid) rhs += bound * redcost @@ -321,7 +331,7 @@ function setdualsol!(form::Formulation, new_dual_sol::DualSolution)::Tuple{Bool, cur_dual_sol_varbounds = @view dual_sols_varbounds[:,cur_sol_id] for (var_id, var_val) in cur_dual_sol_varbounds - if factor * new_dual_sol.supp_data[var_id][1] != var_val[1] || new_dual_sol.supp_data[var_id][2] != var_val[2] + if factor * get_var_redcosts(new_dual_sol)[var_id][1] != var_val[1] || get_var_redcosts(new_dual_sol)[var_id][2] != var_val[2] is_identical = false break end @@ -521,7 +531,7 @@ function setsinglevarconstr!( sense::ConstrSense = Greater, inc_val::Float64 = 0.0, is_active::Bool = true, - id = generateconstrid(duty, form) + id = generatesinglevarconstrid(duty, form) ) c_data = ConstrData(rhs, kind, sense, inc_val, is_active, true) constr = SingleVarConstraint(id, varid, name; constr_data = c_data) @@ -722,7 +732,7 @@ end function constraint_primal(primalsol::PrimalSolution, constrid::ConstrId) val = 0.0 - for (varid, coeff) in @view getcoefmatrix(primalsol.model)[constrid, :] + for (varid, coeff) in @view getcoefmatrix(getmodel(primalsol))[constrid, :] val += coeff * primalsol[varid] end return val diff --git a/src/MathProg/manager.jl b/src/MathProg/manager.jl index ea3a9556e..df3f4baa7 100644 --- a/src/MathProg/manager.jl +++ b/src/MathProg/manager.jl @@ -10,14 +10,17 @@ 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) +# The formulation manager is an internal data structure that contains & manager +# all the elements which constitute a MILP formulation: variables, constraints, +# objective constant (costs stored in variables), coefficient matrix, +# cut generators (that contain cut callbacks)... mutable struct FormulationManager vars::Dict{VarId, Variable} constrs::Dict{ConstrId, Constraint} - single_var_constrs::Dict{ConstrId, SingleVarConstraint} - single_var_constrs_per_var::Dict{VarId, Dict{ConstrId, SingleVarConstraint}} # ids of the constraint of type : single variable >= bound + single_var_constrs::Dict{SingleVarConstrId, SingleVarConstraint} + single_var_constrs_per_var::Dict{VarId, Dict{SingleVarConstrId, SingleVarConstraint}} # ids of the constraint of type : single variable >= bound objective_constant::Float64 coefficients::ConstrVarMatrix # rows = constraints, cols = variables - # expressions::VarVarMatrix # cols = variables, rows = expressions (not implemented yet) primal_sols::VarVarMatrix # cols = primal solutions with varid, rows = variables primal_sols_custom_data::Dict{VarId, BD.AbstractCustomData} primal_sol_costs::DynSparseVector{VarId} # primal solutions with varid map to their cost @@ -34,11 +37,10 @@ function FormulationManager(; custom_families_id = Dict{BD.AbstractCustomData,In return FormulationManager( vars, constrs, - Dict{ConstrId, SingleVarConstraint}(), - Dict{VarId, Dict{ConstrId, SingleVarConstraint}}(), + Dict{SingleVarConstrId, SingleVarConstraint}(), + Dict{VarId, Dict{SingleVarConstrId, SingleVarConstraint}}(), 0.0, dynamicsparse(ConstrId, VarId, Float64), - #dynamicsparse(VarId, VarId, Float64; fill_mode = false), dynamicsparse(VarId, VarId, Float64; fill_mode = false), Dict{VarId,Any}(), dynamicsparsevec(VarId[], Float64[]), @@ -50,6 +52,7 @@ function FormulationManager(; custom_families_id = Dict{BD.AbstractCustomData,In ) end +# Internal method to store a Variable in the formulation manager. function _addvar!(m::FormulationManager, var::Variable) if haskey(m.vars, var.id) error(string( @@ -61,6 +64,8 @@ function _addvar!(m::FormulationManager, var::Variable) return end +# Internal methods to store a Constraint or a SingleVarConstraint in the +# formulation manager. function _addconstr!(m::FormulationManager, constr::Constraint) if haskey(m.constrs, constr.id) error(string( diff --git a/src/MathProg/solutions.jl b/src/MathProg/solutions.jl index 582b4547d..d3a79050f 100644 --- a/src/MathProg/solutions.jl +++ b/src/MathProg/solutions.jl @@ -62,7 +62,8 @@ Create a dual solution to the formulation `form` of cost `cost` and status `stat The first representation of the dual solution is mandatory. It contains `constrids` the set of ids of the constraints and `constrvals` the values of the constraints (`constrvals[i]` is dual value of `constrids[i]`). -It also contains `varvals[i]` the dual values of the bound constraint `varactivebounds[i]` of the variables `varids`. +It also contains `varvals[i]` the dual values of the bound constraint `varactivebounds[i]` of the variables `varids` +(also known as the reduced cost). The user can also attach to the dual solution a customized representation `custom_data`. @@ -81,6 +82,8 @@ function DualSolution( return DualSolution{M}(sol, var_redcosts, custom_data) end +get_var_redcosts(s::DualSolution) = s.var_redcosts + # Redefine methods from ColunaBase to access the formulation, the value, the # status of a Solution, and other specific information ColunaBase.getmodel(s::AbstractSolution) = getmodel(s.solution) @@ -88,6 +91,7 @@ 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) @@ -189,3 +193,14 @@ function Base.show(io::IO, solution::PrimalSolution{M}) where {M} end 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 diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index e025f0b3a..d7673bedd 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -501,6 +501,7 @@ function Base.delete!(form::Formulation, constr::Constraint) remove!(form.buffer, constrid) return end +Base.delete!(form::Formulation, id::ConstrId) = delete!(form, getconstr(form, id)) function Base.delete!(form::Formulation, constr::SingleVarConstraint) constrid = getid(constr) @@ -508,14 +509,7 @@ function Base.delete!(form::Formulation, constr::SingleVarConstraint) delete!(form.manager.single_var_constrs_per_var[constr.varid], constrid) return end - -function Base.delete!(form::Formulation, id::ConstrId) - constr = getconstr(form, id) - if constr === nothing - return delete!(form, getsinglevarconstr(form, id)) - end - return delete!(form, constr) -end +Base.delete!(form::Formulation, id::SingleVarConstrId) = delete!(form, getconstr(form, id)) ## explicit """ @@ -525,9 +519,9 @@ end Return `true` if a variable or a constraint is explicit in a formulation; `false` otherwise. """ isexplicit(form::Formulation, varid::VarId) = isexplicit(form, getvar(form, varid)) -isexplicit(form::Formulation, var::Variable) = var.perendata.is_explicit +isexplicit(::Formulation, var::Variable) = var.perendata.is_explicit isexplicit(form::Formulation, constrid::ConstrId) = isexplicit(form, getconstr(form, constrid)) -isexplicit(form::Formulation, constr::Constraint) = constr.perendata.is_explicit +isexplicit(::Formulation, constr::Constraint) = constr.perendata.is_explicit ## name """ @@ -537,9 +531,9 @@ isexplicit(form::Formulation, constr::Constraint) = constr.perendata.is_explicit Return the name of a variable or a constraint in a formulation. """ getname(form::Formulation, varid::VarId) = getvar(form, varid).name -getname(form::Formulation, var::Variable) = var.name +getname(::Formulation, var::Variable) = var.name getname(form::Formulation, constrid::ConstrId) = getconstr(form, constrid).name -getname(form::Formulation, constr::Constraint) = constr.name +getname(::Formulation, constr::Constraint) = constr.name ## branching_priority """ @@ -549,7 +543,7 @@ getname(form::Formulation, constr::Constraint) = constr.name Return the branching priority of a variable """ getbranchingpriority(form::Formulation, varid::VarId) = getvar(form, varid).branching_priority -getbranchingpriority(form::Formulation, var::Variable) = var.branching_priority +getbranchingpriority(::Formulation, var::Variable) = var.branching_priority # Reset """ diff --git a/src/MathProg/variable.jl b/src/MathProg/variable.jl index a8108e25e..04c31b79e 100644 --- a/src/MathProg/variable.jl +++ b/src/MathProg/variable.jl @@ -74,7 +74,7 @@ setkind!(record::MoiVarRecord, kind::MoiVarKind) = record.kind = kind Representation of a variable in Coluna. """ mutable struct Variable <: AbstractVarConstr - id::Id{Variable} + id::Id{Variable,:usual} name::String perendata::VarData curdata::VarData @@ -83,7 +83,7 @@ mutable struct Variable <: AbstractVarConstr custom_data::Union{Nothing, BD.AbstractCustomData} end -const VarId = Id{Variable} +const VarId = Id{Variable,:usual} getid(var::Variable) = var.id diff --git a/src/MathProg/vcids.jl b/src/MathProg/vcids.jl index 59d002019..a6b706988 100644 --- a/src/MathProg/vcids.jl +++ b/src/MathProg/vcids.jl @@ -10,7 +10,7 @@ It is composed by the following uids: 4. `proc_uid`: Number of the process where it was generated For a origin jump var/constr the origin_form_uid is the jump model while the assigned_form_uid_in_reformulation is the spform for a pure spform and the master for a pure master var. For a added var/constr the origin_form_uid is where is was created : for instance a master column 's orginal formulation is the subproblem for which it was a solution and is assigned formulation is the master program.Number of the process where it was generated """ -struct Id{VC <: AbstractVarConstr} +struct Id{VC <: AbstractVarConstr,F} # F is a flag to differenciate ConstrId & SingleVarConstrId duty::Duty{VC} uid::Int32 origin_form_uid::FormId @@ -24,54 +24,54 @@ _create_hash(uid, origin_form_uid, proc_uid) = uid * MAX_NB_FORMULATIONS * MAX_NB_PROCESSES + origin_form_uid * MAX_NB_PROCESSES + proc_uid -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id) where {VC} +function Id{VC,F}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id) where {VC,F} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC,F}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid) where {VC} +function Id{VC,F}(duty::Duty{VC}, uid, origin_form_uid) where {VC,F} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC,F}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id) where {VC} +function Id{VC,F}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id) where {VC,F} proc_uid = Distributed.myid() - Id{VC}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) + Id{VC,F}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}, assigned_form_uid_in_reformulation) where {VC} - Id{VC}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, id._hash) +function Id{VC,F}(duty::Duty{VC}, id::Id{VC,F}, assigned_form_uid_in_reformulation) where {VC,F} + Id{VC,F}(duty, id.uid, id.origin_form_uid, assigned_form_uid_in_reformulation, id.proc_uid, id.custom_family_id, id._hash) end -function Id{VC}(duty::Duty{VC}, id::Id{VC}; custom_family_id = id.custom_family_id) where {VC} - Id{VC}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, id._hash) +function Id{VC,F}(duty::Duty{VC}, id::Id{VC,F}; custom_family_id = id.custom_family_id) where {VC,F} + Id{VC,F}(duty, id.uid, id.origin_form_uid, id.assigned_form_uid_in_reformulation, id.proc_uid, custom_family_id, id._hash) end Base.hash(a::Id, h::UInt) = hash(a._hash, h) -Base.isequal(a::Id{VC}, b::Id{VC}) where {VC} = Base.isequal(a._hash, b._hash) +Base.isequal(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = Base.isequal(a._hash, b._hash) Base.isequal(a::Int, b::Id) = Base.isequal(a, b._hash) Base.isequal(a::Id, b::Int) = Base.isequal(a._hash, b) -Base.isless(a::Id{VC}, b::Id{VC}) where {VC} = Base.isless(a._hash, b._hash) -Base.zero(I::Type{Id{VC}}) where {VC} = I(Duty{VC}(0), -1, -1, -1, -1, -1, -1) +Base.isless(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = Base.isless(a._hash, b._hash) +Base.zero(I::Type{Id{VC,F}}) where {VC,F} = I(Duty{VC}(0), -1, -1, -1, -1, -1, -1) -Base.:(<)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash < b._hash -Base.:(<=)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash <= b._hash -Base.:(==)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash == b._hash -Base.:(>)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash > b._hash -Base.:(>=)(a::Id{VC}, b::Id{VC}) where {VC} = a._hash >= b._hash +Base.:(<)(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = a._hash < b._hash +Base.:(<=)(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = a._hash <= b._hash +Base.:(==)(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = a._hash == b._hash +Base.:(>)(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = a._hash > b._hash +Base.:(>=)(a::Id{VC,F}, b::Id{VC,F}) where {VC,F} = a._hash >= b._hash getuid(id::Id) = id.uid -getduty(vcid::Id{VC}) where {VC} = vcid.duty +getduty(vcid::Id{VC,F}) where {VC,F} = vcid.duty getoriginformuid(id::Id) = id.origin_form_uid getassignedformuid(id::Id) = id.assigned_form_uid_in_reformulation getprocuid(id::Id) = id.proc_uid getsortuid(id::Id) = getuid(id) + 1000000 * getoriginformuid(id) -function Base.show(io::IO, id::Id{T}) where {T} - print(io, T, "#", +function Base.show(io::IO, id::Id{T,F}) where {T,F} + print(io, T, "#(", F ,")", "u", id.uid, "f", id.origin_form_uid, "a", id.assigned_form_uid_in_reformulation, - "p", id.proc_uid , + "p", id.proc_uid, "h", id._hash) end diff --git a/test/unit/containers/solsandbounds.jl b/test/unit/containers/solsandbounds.jl index f6697affd..97897b2e2 100644 --- a/test/unit/containers/solsandbounds.jl +++ b/test/unit/containers/solsandbounds.jl @@ -283,16 +283,16 @@ function solution_unit() @testset "Solution" begin model = FakeModel() - Solution = CB.Solution{FakeModel,Int,Float64,Nothing} + Solution = CB.Solution{FakeModel,Int,Float64} dict_sol, soldecs, solvals = fake_solution_factory(100) - primal_sol = Solution(model, soldecs, solvals, 12.3, CB.FEASIBLE_SOL, nothing, nothing) + primal_sol = Solution(model, soldecs, solvals, 12.3, CB.FEASIBLE_SOL) test_solution_iterations(primal_sol, dict_sol) @test CB.getvalue(primal_sol) == 12.3 @test CB.getstatus(primal_sol) == CB.FEASIBLE_SOL 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, Coluna.ColunaBase.FEASIBLE_SOL, nothing, nothing) + primal_sol = Solution(model, collect(keys(dict_sol)), collect(values(dict_sol)), 0.0, Coluna.ColunaBase.FEASIBLE_SOL) @test iterate(primal_sol) == iterate(primal_sol.sol) _, state = iterate(primal_sol) diff --git a/test/unit/optimizationstate.jl b/test/unit/optimizationstate.jl index c86ba91db..eecb4d53b 100644 --- a/test/unit/optimizationstate.jl +++ b/test/unit/optimizationstate.jl @@ -56,7 +56,7 @@ function update_sol_tests() # check that incumbent bound is updated @test get_lp_dual_bound(state) == 1.0 update_lp_dual_sol!(state, DualSolution( - form, [getid(constr)], [0.0], 0.0, CB.UNKNOWN_FEASIBILITY + form, [getid(constr)], [0.0], VarId[], Float64[], ActiveBound[], 0.0, CB.UNKNOWN_FEASIBILITY )) # check that solution worse than `dualsol` is NOT added to `state.lp_dual_sols` @test length(get_lp_dual_sols(state)) == 1 diff --git a/test/unit/variable.jl b/test/unit/variable.jl index d43da15f7..046cf1146 100644 --- a/test/unit/variable.jl +++ b/test/unit/variable.jl @@ -33,7 +33,7 @@ function variable_getters_and_setters_tests() ) v = ClF.Variable( - ClF.Id{ClF.Variable}(ClF.MasterPureVar, 23, 10, false), "fake_var"; + ClF.VarId(ClF.MasterPureVar, 23, 10, false), "fake_var"; var_data = v_data ) From d6ca4db449d526b3aa7c42850acf41cfa00c96fb Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 3 Sep 2021 17:40:54 +0200 Subject: [PATCH 12/19] clean unused methods --- src/Algorithm/benders.jl | 20 ++++++++++---------- src/Algorithm/treesearch.jl | 1 - src/MOIwrapper.jl | 2 +- src/MathProg/MathProg.jl | 2 -- src/MathProg/formulation.jl | 5 ----- src/MathProg/vcids.jl | 10 ---------- 6 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/Algorithm/benders.jl b/src/Algorithm/benders.jl index 9817af2fe..bb9a48210 100644 --- a/src/Algorithm/benders.jl +++ b/src/Algorithm/benders.jl @@ -1,5 +1,4 @@ @with_kw struct BendersCutGeneration <: AbstractOptimizationAlgorithm - option_use_reduced_cost::Bool = false option_increase_cost_in_hybrid_phase::Bool = false feasibility_tol::Float64 = 1e-5 optimality_tol::Float64 = Coluna.DEF_OPTIMALITY_ATOL @@ -110,15 +109,16 @@ function update_benders_sp_problem!( setcurub!(spform, var, getperenub(spform, var) - master_primal_sol[varid]) end - if algo.option_use_reduced_cost - for (varid, var) in getvars(spform) - iscuractive(spform, varid) || continue - getduty(varid) <= BendSpSlackFirstStageVar || continue - cost = getcurcost(spform, var) - rc = computereducedcost(masterform, varid, master_dual_sol) - setcurcost!(spform, var, rc) - end - end + # TODO : refactor this option (it was untested) + # if algo.option_use_reduced_cost + # for (varid, var) in getvars(spform) + # iscuractive(spform, varid) || continue + # getduty(varid) <= BendSpSlackFirstStageVar || continue + # cost = getcurcost(spform, var) + # rc = computereducedcost(masterform, varid, master_dual_sol) + # setcurcost!(spform, var, rc) + # end + # end return false end diff --git a/src/Algorithm/treesearch.jl b/src/Algorithm/treesearch.jl index 7934e4fc7..1f9f8b966 100644 --- a/src/Algorithm/treesearch.jl +++ b/src/Algorithm/treesearch.jl @@ -318,7 +318,6 @@ function updatedualbound!(data::TreeSearchRuntimeData) worst_bound = data.worst_db_of_pruned_node end set_ip_dual_bound!(treestate, worst_bound) - set_lp_dual_bound!(treestate, worst_bound) return end diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 60520ddf1..e0835e19d 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -420,7 +420,7 @@ function MOI.get( ) where {F<:MOI.SingleVariable, S} indices = MOI.ConstraintIndex{F,S}[] for (id, _) in model.constrs_on_single_var - if S === typeof(MOI.get(model, MOI.ConstraintSet(), id)) + if S == typeof(MOI.get(model, MOI.ConstraintSet(), id)) push!(indices, id) end end diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index abfb78f2b..f84e76176 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -53,8 +53,6 @@ export no_optimizer_builder, set_original_formulation!, getid, getuid, enforce_integrality!, relax_integrality!, getobjsense, getoptimizer, getoptimizers, - setdualbound!, - computereducedcost, update!, getduty, computereducedrhs, diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 3d8061978..93c165bad 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -699,11 +699,6 @@ function set_objective_sense!(form::Formulation, min::Bool) return end -function computesolvalue(form::Formulation, sol_vec::AbstractDict{VarId, Float64}) - val = sum(getperencost(form, varid) * value for (varid, value) in sol_vec) - return val -end - # TODO : remove (unefficient & specific to an algorithm) function computereducedcost(form::Formulation, varid::VarId, dualsol::DualSolution) redcost = getperencost(form, varid) diff --git a/src/MathProg/vcids.jl b/src/MathProg/vcids.jl index a6b706988..3520942ff 100644 --- a/src/MathProg/vcids.jl +++ b/src/MathProg/vcids.jl @@ -24,16 +24,6 @@ _create_hash(uid, origin_form_uid, proc_uid) = uid * MAX_NB_FORMULATIONS * MAX_NB_PROCESSES + origin_form_uid * MAX_NB_PROCESSES + proc_uid -function Id{VC,F}(duty::Duty{VC}, uid, origin_form_uid, assigned_form_uid, custom_family_id) where {VC,F} - proc_uid = Distributed.myid() - Id{VC,F}(duty, uid, origin_form_uid, assigned_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) -end - -function Id{VC,F}(duty::Duty{VC}, uid, origin_form_uid) where {VC,F} - proc_uid = Distributed.myid() - Id{VC,F}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, -1, _create_hash(uid, origin_form_uid, proc_uid)) -end - function Id{VC,F}(duty::Duty{VC}, uid, origin_form_uid, custom_family_id) where {VC,F} proc_uid = Distributed.myid() Id{VC,F}(duty, uid, origin_form_uid, origin_form_uid, proc_uid, custom_family_id, _create_hash(uid, origin_form_uid, proc_uid)) From e1f64e7d0cd614c8ebda307e7871c7aed39db8ba Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 3 Sep 2021 19:03:35 +0200 Subject: [PATCH 13/19] add tests --- src/MathProg/MOIinterface.jl | 10 +-- src/MathProg/buffer.jl | 2 +- src/MathProg/constraint.jl | 23 ++++-- src/MathProg/formulation.jl | 20 +---- src/MathProg/varconstr.jl | 112 +++++++++++++++++---------- test/MathOptInterface/MOI_wrapper.jl | 44 +++++------ test/unit/constraint.jl | 28 ++++++- 7 files changed, 143 insertions(+), 96 deletions(-) diff --git a/src/MathProg/MOIinterface.jl b/src/MathProg/MOIinterface.jl index 9c0ba632b..be94ac63e 100644 --- a/src/MathProg/MOIinterface.jl +++ b/src/MathProg/MOIinterface.jl @@ -60,9 +60,9 @@ function update_obj_const_in_optimizer!(form::Formulation, optimizer::MoiOptimiz return end -function update_constr_member_in_optimizer!(optimizer::MoiOptimizer, - c::Constraint, v::Variable, - coeff::Float64) +function update_constr_member_in_optimizer!( + optimizer::MoiOptimizer, c::Constraint, v::Variable, coeff::Float64 +) moi_c_index = getindex(getmoirecord(c)) moi_v_index = getindex(getmoirecord(v)) MOI.modify( @@ -156,9 +156,7 @@ end function remove_from_optimizer!(form::Formulation, optimizer::MoiOptimizer, ids::Set{I}) where {I<:Id} for id in ids - vc = getelem(form, id) - @logmsg LogLevel(-3) string("Removing varconstr of name ", getname(form, vc)) - remove_from_optimizer!(form, optimizer, vc) + remove_from_optimizer!(form, optimizer, getelem(form, id)) end return end diff --git a/src/MathProg/buffer.jl b/src/MathProg/buffer.jl index e63cad974..3b949cca2 100644 --- a/src/MathProg/buffer.jl +++ b/src/MathProg/buffer.jl @@ -67,7 +67,7 @@ function remove!(buffer::FormulationBuffer, varid::VarId) end # Since there is no efficient way to remove changes done to the coefficient matrix, -# we propagate them if the constraint is active and explicit +# we propagate them if and only if the constraint is active and explicit function remove!(buffer::FormulationBuffer, constrid::ConstrId) remove!(buffer.constr_buffer, constrid) delete!(buffer.changed_rhs, constrid) diff --git a/src/MathProg/constraint.jl b/src/MathProg/constraint.jl index 1bf8722e0..09ac4ab93 100644 --- a/src/MathProg/constraint.jl +++ b/src/MathProg/constraint.jl @@ -1,7 +1,6 @@ """ - ConstrData - -Information that defines a state of a constraint. These are the fields of a constraint that might change during the solution procedure. +Information that defines a state of a constraint. +These data might change during the optimisation procedure. """ mutable struct ConstrData <: AbstractVcData rhs::Float64 @@ -37,12 +36,19 @@ MoiConstrRecord(;index = MoiConstrIndex()) = MoiConstrRecord(index) getindex(record::MoiConstrRecord) = record.index setindex!(record::MoiConstrRecord, index::MoiConstrIndex) = record.index = index +""" +There are 2 types of constraints in Coluna (i.e. Constraint & SingleVarConstraint). +Both of them inherits from AbstractConstraint because their setters and getters +are quite similar. +""" +abstract type AbstractConstraint <: AbstractVarConstr end + """ Representation of a constraint in Coluna. Coefficients of variables involved in the constraints are stored in the coefficient matrix. If the constraint involves only one variable, you should use a `SingleVarConstraint`. """ -mutable struct Constraint <: AbstractVarConstr +mutable struct Constraint <: AbstractConstraint id::Id{Constraint,:usual} name::String perendata::ConstrData @@ -54,6 +60,7 @@ end const ConstrId = Id{Constraint,:usual} +# Internal use only, see `MathProg.setconstr!` to create a constraint. function Constraint( id::ConstrId, name::String; constr_data = ConstrData(), moi_index::MoiConstrIndex = MoiConstrIndex(), @@ -67,9 +74,12 @@ end """ Representation of a single variable constraint in Coluna : lb <= 1*var <= ub. -For performance reasons, Coluna does not store these constraints in the coefficient matrix. +For performance reasons, Coluna does not store these constraints in the coefficient matrix +and they are never pushed in the subsolver of a formulation. +Coluna takes into account those constraints only during the bound propagation operation +which updates the current upper and lower bounds of the variable. """ -mutable struct SingleVarConstraint <: AbstractVarConstr +mutable struct SingleVarConstraint <: AbstractConstraint id::Id{Constraint,:single} name::String varid::VarId @@ -79,6 +89,7 @@ end const SingleVarConstrId = Id{Constraint,:single} +# Internal use only, see `MathProg.setsinglevarconstr!` to create a single var constraint. function SingleVarConstraint( id::SingleVarConstrId, varid::VarId, name::String; constr_data = ConstrData() ) diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 93c165bad..b9e347d5e 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -699,21 +699,6 @@ function set_objective_sense!(form::Formulation, min::Bool) return end -# TODO : remove (unefficient & specific to an algorithm) -function computereducedcost(form::Formulation, varid::VarId, dualsol::DualSolution) - redcost = getperencost(form, varid) - coefficient_matrix = getcoefmatrix(form) - sign = 1 - if getobjsense(form) == MinSense - sign = -1 - end - for (constrid, dual_val) in dualsol - coeff = coefficient_matrix[constrid, varid] - redcost += sign * dual_val * coeff - end - return redcost -end - # TODO : remove (unefficient & specific to Benders) function computereducedrhs(form::Formulation{BendersSp}, constrid::ConstrId, primalsol::PrimalSolution) constrrhs = getperenrhs(form,constrid) @@ -743,7 +728,6 @@ end ############################################################################################ # Bounds propagation ############################################################################################ -# if a new single var constraint has been added, we should call this method function _update_var_cur_lb!(form::Formulation, var::Variable, lb, constrid) cur_lb = getcurlb(form, var) @@ -784,6 +768,8 @@ function _update_bounds!(form::Formulation, var::Variable, ::Val{Equal}, rhs, co return end +# If some new single var constraints have been added to a formulation, Coluna +# should call this method. function bounds_propagation!(form::Formulation) for (constrid, constr) in form.manager.single_var_constrs var = getvar(form, constr.varid) @@ -854,13 +840,11 @@ function _show_singlevarconstraint(io::IO, form::Formulation, constr::SingleVarC end function _show_singlevarconstraints(io::IO, form::Formulation) - println("----------------------------------") for (varid, _) in getvars(form) for (_, constr) in getsinglevarconstrs(form, varid) _show_singlevarconstraint(io, form, constr) end end - println("----------------------------------") return end diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index d7673bedd..24b5f0369 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -11,7 +11,7 @@ getoriginformuid(vc::AbstractVarConstr) = getoriginformuid(getid(vc)) Return the cost as defined by the user of a variable in a formulation. """ getperencost(form::Formulation, varid::VarId) = getperencost(form, getvar(form, varid)) -getperencost(form::Formulation, var::Variable) = var.perendata.cost +getperencost(::Formulation, var::Variable) = var.perendata.cost """ getcurcost(formulation, variable) @@ -20,7 +20,7 @@ getperencost(form::Formulation, var::Variable) = var.perendata.cost Return the current cost of the variable in the formulation. """ getcurcost(form::Formulation, varid::VarId) = getcurcost(form, getvar(form, varid)) -getcurcost(form::Formulation, var::Variable) = var.curdata.cost +getcurcost(::Formulation, var::Variable) = var.curdata.cost """ setperencost!(formulation, variable, cost) @@ -63,7 +63,7 @@ end Return the lower bound as defined by the user of a variable in a formulation. """ getperenlb(form::Formulation, varid::VarId) = getperenlb(form, getvar(form, varid)) -getperenlb(form::Formulation, var::Variable) = var.perendata.lb +getperenlb(::Formulation, var::Variable) = var.perendata.lb """ getcurlb(formulation, varid) @@ -72,7 +72,7 @@ getperenlb(form::Formulation, var::Variable) = var.perendata.lb Return the current lower bound of a variable in a formulation. """ getcurlb(form::Formulation, varid::VarId) = getcurlb(form, getvar(form, varid)) -getcurlb(form::Formulation, var::Variable) = var.curdata.lb +getcurlb(::Formulation, var::Variable) = var.curdata.lb """ setcurlb!(formulation, varid, lb::Float64) @@ -99,7 +99,7 @@ setcurlb!(form::Formulation, varid::VarId, lb::Float64) = setcurlb!(form, getva Return the upper bound as defined by the user of a variable in a formulation. """ getperenub(form::Formulation, varid::VarId) = getperenub(form, getvar(form, varid)) -getperenub(form::Formulation, var::Variable) = var.perendata.ub +getperenub(::Formulation, var::Variable) = var.perendata.ub """ getcurub(formulation, varid) @@ -108,7 +108,7 @@ getperenub(form::Formulation, var::Variable) = var.perendata.ub Return the current upper bound of a variable in a formulation. """ getcurub(form::Formulation, varid::VarId) = getcurub(form, getvar(form, varid)) -getcurub(form::Formulation, var::Variable) = var.curdata.ub +getcurub(::Formulation, var::Variable) = var.curdata.ub """ setcurub!(formulation, varid, ub::Float64) @@ -137,8 +137,8 @@ setcurub!(form::Formulation, varid::VarId, ub::Float64) = setcurub!(form, getvar Return the right-hand side as defined by the user of a constraint in a formulation. """ getperenrhs(form::Formulation, constrid::ConstrId) = getperenrhs(form, getconstr(form, constrid)) -getperenrhs(form::Formulation, constr::Constraint) = constr.perendata.rhs -getperenrhs(form::Formulation, constr::SingleVarConstraint) = constr.perendata.rhs +getperenrhs(form::Formulation, constrid::SingleVarConstrId) = getperenrhs(form, getconstr(form, constrid)) +getperenrhs(::Formulation, constr::AbstractConstraint) = constr.perendata.rhs """ getcurrhs(formulation, constraint) @@ -147,8 +147,8 @@ getperenrhs(form::Formulation, constr::SingleVarConstraint) = constr.perendata.r Return the current right-hand side of a constraint in a formulation. """ getcurrhs(form::Formulation, constrid::ConstrId) = getcurrhs(form, getconstr(form, constrid)) -getcurrhs(form::Formulation, constr::Constraint) = constr.curdata.rhs -getcurrhs(form::Formulation, constr::SingleVarConstraint) = constr.curdata.rhs +getcurrhs(form::Formulation, constrid::SingleVarConstrId) = getcurrhs(form, getconstr(form, constrid)) +getcurrhs(form::Formulation, constr::AbstractConstraint) = constr.curdata.rhs """ setperenrhs!(formulation, constr, rhs) @@ -157,11 +157,15 @@ getcurrhs(form::Formulation, constr::SingleVarConstraint) = constr.curdata.rhs Set the perennial rhs of a constraint in a formulation. Change is propagated to the current rhs of the constraint. """ -function setperenrhs!(form::Formulation, constr::Constraint, rhs) +function setperenrhs!(form::Formulation, constr::AbstractConstraint, rhs) constr.perendata.rhs = rhs return setcurrhs!(form, constr, rhs) end -setperenrhs!(form::Formulation, constrid::ConstrId, rhs) = setperenrhs!(form, getconstr(form, constrid), rhs) +setperenrhs!(form::Formulation, constrid::ConstrId, rhs) = + setperenrhs!(form, getconstr(form, constrid), rhs) + +setperenrhs!(form::Formulation, constrid::SingleVarConstrId, rhs) = + setperenrhs!(form, getconstr(form, constrid), rhs) """ setcurrhs(formulation, constraint, rhs::Float64) @@ -170,6 +174,9 @@ setperenrhs!(form::Formulation, constrid::ConstrId, rhs) = setperenrhs!(form, ge Set the current right-hand side of a constraint in a formulation. If the constraint is active and explicit, this change is buffered before application to the subsolver. + +**Warning** : if you change the rhs of a single variable constraint, make sure that you +perform bound propagation before calling the subsolver of the formulation. """ function setcurrhs!(form::Formulation, constr::Constraint, rhs::Float64) constr.curdata.rhs = rhs @@ -178,7 +185,15 @@ function setcurrhs!(form::Formulation, constr::Constraint, rhs::Float64) end return end -setcurrhs!(form::Formulation, constrid::ConstrId, rhs::Float64) = setcurrhs!(form, getconstr(form, constrid), rhs) + +setcurrhs!(form::Formulation, constrid::ConstrId, rhs::Float64) = + setcurrhs!(form, getconstr(form, constrid), rhs) + +setcurrhs!(form::Formulation, constr::SingleVarConstraint, rhs::Float64) = + constr.curdata.rhs = rhs + +setcurrhs!(form::Formulation, constrid::SingleVarConstrId, rhs::Float64) = + setcurrhs!(form, getconstr(form, constrid), rhs) # Variable & Constraints ## kind @@ -201,6 +216,7 @@ getperenkind(form::Formulation, varid::VarId) = getperenkind(form, getvar(form, getperenkind(form::Formulation, var::Variable) = var.perendata.kind getperenkind(form::Formulation, constrid::ConstrId) = getperenkind(form, getconstr(form, constrid)) getperenkind(form::Formulation, constr::Constraint) = constr.perendata.kind +getperenkind(form::Formulation, constrid::SingleVarConstrId) = getperenkind(form, getconstr(form, constrid)) getperenkind(form::Formulation, constr::SingleVarConstraint) = constr.perendata.kind """ @@ -263,8 +279,8 @@ The perennial sense of a variable depends on its perennial bounds. getperensense(form::Formulation, varid::VarId) = getperensense(form, getvar(form, varid)) getperensense(form::Formulation, var::Variable) = _senseofvar(getperenlb(form, var), getperenub(form, var)) getperensense(form::Formulation, constrid::ConstrId) = getperensense(form, getconstr(form, constrid)) -getperensense(form::Formulation, constr::Constraint) = constr.perendata.sense -getperensense(form::Formulation, constr::SingleVarConstraint) = constr.perendata.sense +getperensense(form::Formulation, constrid::SingleVarConstrId) = getperensense(form, getconstr(form, constrid)) +getperensense(::Formulation, constr::AbstractConstraint) = constr.perendata.sense """ getcursense(formulation, varconstr) @@ -276,8 +292,8 @@ The current sense of a variable depends on its current bounds. getcursense(form::Formulation, varid::VarId) = getcursense(form, getconstr(form, varid)) getcursense(form::Formulation, var::Variable) = _senseofvar(getcurlb(form, var), getcurub(form, var)) getcursense(form::Formulation, constrid::ConstrId) = getcursense(form, getconstr(form, constrid)) -getcursense(form::Formulation, constr::Constraint) = constr.curdata.sense -getcursense(form::Formulation, constr::SingleVarConstraint) = constr.curdata.sense +getcursense(form::Formulation, constrid::SingleVarConstrId) = getcursense(form, getconstr(form, constrid)) +getcursense(::Formulation, constr::AbstractConstraint) = constr.curdata.sense """ setperensense!(form, constr, sense) @@ -300,6 +316,9 @@ Set the current sense of a constraint in a formulation. This method is not applicable to variables because the sense of a variable depends on its bounds. + +**Warning** : if you set the sense of a single var constraint, make sure you perform bound +propagation before calling the subsolver of the formulation. """ function setcursense!(form::Formulation, constr::Constraint, sense::ConstrSense) constr.curdata.sense = sense @@ -309,9 +328,14 @@ function setcursense!(form::Formulation, constr::Constraint, sense::ConstrSense) return end -function setcursense!(form::Formulation, constrid::ConstrId, sense::ConstrSense) - return setcursense!(form, getconstr(form, constrid), sense) -end +setcursense!(form::Formulation, constrid::ConstrId, sense::ConstrSense) = + setcursense!(form, getconstr(form, constrid), sense) + +setcursense!(::Formulation, constr::SingleVarConstraint, sense::ConstrSense) = + constr.curdata.sense = sense + +setcursense!(form::Formulation, constrid::SingleVarConstrId, sense::ConstrSense) = + setcursense!(form, getconstr(form, constrid), sense) ## inc_val """ @@ -323,10 +347,10 @@ The incumbent value is the primal value associated to a variable or the dual val a constraint. """ getperenincval(form::Formulation, varid::VarId) = getperenincval(form, getvar(form, varid)) -getperenincval(form::Formulation, var::Variable) = var.perendata.inc_val +getperenincval(::Formulation, var::Variable) = var.perendata.inc_val getperenincval(form::Formulation, constrid::ConstrId) = getperenincval(form, getconstr(form, constrid)) -getperenincval(form::Formulation, constr::Constraint) = constr.perendata.inc_val -getperenincval(form::Formulation, constr::SingleVarConstraint) = constr.perendata.inc_val +getperenincval(form::Formulation, constrid::SingleVarConstrId) = getperenincval(form, getconstr(form, constrid)) +getperenincval(::Formulation, constr::AbstractConstraint) = constr.perendata.inc_val """ getcurincval(formulation, varconstrid) @@ -335,26 +359,27 @@ getperenincval(form::Formulation, constr::SingleVarConstraint) = constr.perendat Return the current incumbent value of a variable or a constraint in a formulation. """ getcurincval(form::Formulation, varid::VarId) = getcurincval(form, getvar(form, varid)) -getcurincval(form::Formulation, var::Variable) = var.curdata.inc_val +getcurincval(::Formulation, var::Variable) = var.curdata.inc_val getcurincval(form::Formulation, constrid::ConstrId) = getcurincval(form, getconstr(form, constrid)) -getcurincval(form::Formulation, constr::Constraint) = constr.curdata.inc_val -getcurincval(form::Formulation, constr::SingleVarConstraint) = constr.curdata.inc_val +getcurincval(form::Formulation, constrid::SingleVarConstrId) = getcurincval(form, getconstr(form, constrid)) +getcurincval(::Formulation, constr::AbstractConstraint) = constr.curdata.inc_val """ setcurincval!(formulation, varconstrid, value::Real) Set the current incumbent value of a variable or a constraint in a formulation. """ -function setcurincval!(form::Formulation, var::Variable, inc_val::Real) +setcurincval!(::Formulation, var::Variable, inc_val::Real) = var.curdata.inc_val = inc_val - return -end -setcurincval!(form::Formulation, varid::VarId, inc_val::Real) = setcurincval!(form, getvar(form, varid), inc_val) -function setcurincval!(form::Formulation, constr::Constraint, inc_val::Real) + +setcurincval!(form::Formulation, varid::VarId, inc_val) = + setcurincval!(form, getvar(form, varid), inc_val) + +setcurincval!(::Formulation, constr::AbstractConstraint, inc_val::Real) = constr.curdata.inc_val = inc_val - return -end -setcurincval!(form::Formulation, constrid::ConstrId, inc_val::Real) = setcurincval!(form, getconstr(form, constrid), inc_val) + +setcurincval!(form::Formulation, constrid::ConstrId, inc_val) = + setcurincval!(form, getconstr(form, constrid), inc_val) ## active """ @@ -367,9 +392,10 @@ deletion of the variable by deativate it. This allows you to keep the variable i to reactivate it later. """ isperenactive(form::Formulation, varid::VarId) = isperenactive(form, getvar(form, varid)) -isperenactive(form::Formulation, var::Variable) = var.perendata.is_active +isperenactive(::Formulation, var::Variable) = var.perendata.is_active isperenactive(form::Formulation, constrid::ConstrId) = isperenactive(form, getconstr(form, constrid)) -isperenactive(form::Formulation, constr::Constraint) = constr.perendata.is_active +isperenactive(form::Formulation, constrid::SingleVarConstrId) = isperenactive(form, getconstr(form, constrid)) +isperenactive(::Formulation, constr::AbstractConstraint) = constr.perendata.is_active """ iscuractive(formulation, varconstrid) @@ -378,10 +404,12 @@ isperenactive(form::Formulation, constr::Constraint) = constr.perendata.is_activ Return `true` if the variable or the constraint is currently active; `false` otherwise. """ iscuractive(form::Formulation, varid::VarId) = iscuractive(form, getvar(form, varid)) -iscuractive(form::Formulation, var::Variable) = var.curdata.is_active +iscuractive(::Formulation, var::Variable) = var.curdata.is_active iscuractive(form::Formulation, constrid::ConstrId) = iscuractive(form, getconstr(form, constrid)) -iscuractive(form::Formulation, constr::Constraint) = constr.curdata.is_active +iscuractive(form::Formulation, constrid::SingleVarConstrId) = iscuractive(form, getconstr(form, constrid)) +iscuractive(::Formulation, constr::AbstractConstraint) = constr.curdata.is_active +## activate! function _activate!(form::Formulation, varconstr::AbstractVarConstr) if isexplicit(form, varconstr) && !iscuractive(form, varconstr) add!(form.buffer, getid(varconstr)) @@ -429,6 +457,7 @@ function activate!(form::Formulation, f::Function) return end +## deactivate! function _deactivate!(form::Formulation, varconstr::AbstractVarConstr) if isexplicit(form, varconstr) && iscuractive(form, varconstr) remove!(form.buffer, getid(varconstr)) @@ -522,6 +551,8 @@ isexplicit(form::Formulation, varid::VarId) = isexplicit(form, getvar(form, vari isexplicit(::Formulation, var::Variable) = var.perendata.is_explicit isexplicit(form::Formulation, constrid::ConstrId) = isexplicit(form, getconstr(form, constrid)) isexplicit(::Formulation, constr::Constraint) = constr.perendata.is_explicit +isexplicit(::Formulation, ::SingleVarConstrId) = true +isexplicit(::Formulation, ::SingleVarConstraint) = true ## name """ @@ -533,7 +564,8 @@ Return the name of a variable or a constraint in a formulation. getname(form::Formulation, varid::VarId) = getvar(form, varid).name getname(::Formulation, var::Variable) = var.name getname(form::Formulation, constrid::ConstrId) = getconstr(form, constrid).name -getname(::Formulation, constr::Constraint) = constr.name +getname(form::Formulation, constrid::SingleVarConstrId) = getconstr(form, constrid).name +getname(::Formulation, constr::AbstractConstraint) = constr.name ## branching_priority """ @@ -545,7 +577,7 @@ Return the branching priority of a variable getbranchingpriority(form::Formulation, varid::VarId) = getvar(form, varid).branching_priority getbranchingpriority(::Formulation, var::Variable) = var.branching_priority -# Reset +# Reset (this method is used only in tests... I don't if we should keep it) """ reset!(form, var) reset!(form, varid) diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index e9f2b1113..3ec2d6fa9 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -87,28 +87,28 @@ end @test JuMP.objective_value(model) == -JuMP.objective_value(model2) end -# @testset "SplitIntervalBridge" begin -# coluna = optimizer_with_attributes( -# Coluna.Optimizer, -# "params" => Coluna.Params( -# solver=Coluna.Algorithm.TreeSearchAlgorithm() -# ), -# "default_optimizer" => GLPK.Optimizer -# ) - -# @axis(M, 1:1) -# J = 1:1 - -# model = BlockModel(coluna) -# @variable(model, x[m in M, j in J]) -# @constraint(model, mult[m in M], 1 <= sum(x[m,j] for j in J) <= 2) -# @objective(model, Max, sum(x[m,j] for m in M, j in J)) - -# @dantzig_wolfe_decomposition(model, decomposition, M) - -# optimize!(model) -# @test JuMP.objective_value(model) == 2.0 -# end +@testset "SplitIntervalBridge" begin + coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver=Coluna.Algorithm.TreeSearchAlgorithm() + ), + "default_optimizer" => GLPK.Optimizer + ) + + @axis(M, 1:1) + J = 1:1 + + model = BlockModel(coluna) + @variable(model, x[m in M, j in J]) + @constraint(model, mult[m in M], 1 <= sum(x[m,j] for j in J) <= 2) + @objective(model, Max, sum(x[m,j] for m in M, j in J)) + + @dantzig_wolfe_decomposition(model, decomposition, M) + + optimize!(model) + @test JuMP.objective_value(model) == 2.0 +end const UNSUPPORTED_TESTS = [ "solve_qcp_edge_cases", # Quadratic constraints not supported diff --git a/test/unit/constraint.jl b/test/unit/constraint.jl index 405b1672f..9b31ebead 100644 --- a/test/unit/constraint.jl +++ b/test/unit/constraint.jl @@ -14,14 +14,14 @@ function moi_constr_record_getters_and_setters_tests() end function constraint_getters_and_setters_tests() - form = create_formulation!(Env(Coluna.Params()), Original()) c = ClF.setconstr!(form, "fake_constr", ClF.MasterBranchOnOrigVarConstr, - rhs = -13.0, kind = ClF.Facultative, sense = ClF.Equal, - inc_val = -12.0, is_active = false, is_explicit = false + rhs = -13.0, kind = ClF.Facultative, sense = ClF.Equal, + inc_val = -12.0, is_active = false, is_explicit = false ) + @test ClF.getcurrhs(form, c) == -13.0 ClF.setcurrhs!(form, c, 10.0) @test ClF.getcurrhs(form,c) == 10.0 @test ClF.getperenrhs(form,c) == -13.0 @@ -29,4 +29,26 @@ function constraint_getters_and_setters_tests() ClF.reset!(form, c) @test ClF.getcurrhs(form, c) == -13.0 @test ClF.getperenrhs(form, c) == -13.0 + + v = ClF.setvar!(form, "x", ClF.OriginalVar) + + c = ClF.setsinglevarconstr!( + form, "fake_single_var_constr", getid(v), ClF.OriginalConstr; rhs = -2.0, + kind = ClF.Essential, sense = ClF.Equal, inc_val = -12.0, is_active = true + ) + + cid = getid(c) + + @test ClF.getcurrhs(form,cid) == -2.0 + ClF.setcurrhs!(form, cid, -10.0) + @test ClF.getcurrhs(form,cid) == -10.0 + @test ClF.getperenrhs(form,cid) == -2.0 + + @test ClF.getcursense(form,cid) == ClF.Equal + ClF.setcursense!(form, cid, ClF.Lower) + @test ClF.getcursense(form,cid) == ClF.Lower + @test ClF.getperensense(form,cid) == ClF.Equal + + @test ClF.getname(form,cid) == "fake_single_var_constr" + return end From 68fb0c1827bf0c20333abdab65c77db4ce57a8b7 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 3 Sep 2021 19:55:31 +0200 Subject: [PATCH 14/19] wip --- src/Algorithm/benders.jl | 2 +- test/unit/constraint.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Algorithm/benders.jl b/src/Algorithm/benders.jl index bb9a48210..ebd4032bd 100644 --- a/src/Algorithm/benders.jl +++ b/src/Algorithm/benders.jl @@ -109,7 +109,7 @@ function update_benders_sp_problem!( setcurub!(spform, var, getperenub(spform, var) - master_primal_sol[varid]) end - # TODO : refactor this option (it was untested) + # TODO : it was untested # if algo.option_use_reduced_cost # for (varid, var) in getvars(spform) # iscuractive(spform, varid) || continue diff --git a/test/unit/constraint.jl b/test/unit/constraint.jl index 9b31ebead..0d27a1546 100644 --- a/test/unit/constraint.jl +++ b/test/unit/constraint.jl @@ -45,8 +45,8 @@ function constraint_getters_and_setters_tests() @test ClF.getperenrhs(form,cid) == -2.0 @test ClF.getcursense(form,cid) == ClF.Equal - ClF.setcursense!(form, cid, ClF.Lower) - @test ClF.getcursense(form,cid) == ClF.Lower + ClF.setcursense!(form, cid, ClF.Less) + @test ClF.getcursense(form,cid) == ClF.Less @test ClF.getperensense(form,cid) == ClF.Equal @test ClF.getname(form,cid) == "fake_single_var_constr" From 69cf39eebbdb7437c033ff8fff40ca2153afd9f2 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 4 Sep 2021 14:33:59 +0200 Subject: [PATCH 15/19] solutions.jl ok --- src/MathProg/solutions.jl | 40 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/MathProg/solutions.jl b/src/MathProg/solutions.jl index d3a79050f..a58fecaa5 100644 --- a/src/MathProg/solutions.jl +++ b/src/MathProg/solutions.jl @@ -119,29 +119,28 @@ function Base.isless(s1::DualSolution, s2::DualSolution) return s1.solution.bound > s2.solution.bound end -function contains(sol::PrimalSolution, f::Function) - for (varid, _) in sol - f(varid) && return true +function contains(sol::AbstractSolution, f::Function) + for (elemid, _) in sol + f(elemid) && return true end return false end -function contains(sol::DualSolution, f::Function) - for (constrid, _) in sol - f(constrid) && return true - end - return false -end - -function _assert_same_model(sols::NTuple{N, S}) where {N,S<:AbstractSolution} +function _sols_from_same_model(sols::NTuple{N, S}) where {N,S<:AbstractSolution} for i in 2:length(sols) getmodel(sols[i-1]) != getmodel(sols[i]) && return false end return true end +# Method `cat` is not implemented for a set of DualSolutions because @guimarqu don't know +# how to concatenate var red cost of a variable if both bounds are active in different +# solutions and because we don't need it for now. function Base.cat(sols::PrimalSolution...) - _assert_same_model(sols) || error("Cannot concatenate solutions not attached to the same model.") + if !_sols_from_same_model(sols) + error("Cannot concatenate solutions not attached to the same model.") + end + ids = VarId[] vals = Float64[] for sol in sols, (id, value) in sol @@ -153,23 +152,6 @@ function Base.cat(sols::PrimalSolution...) ) end -function Base.cat(sols::DualSolution...) - _assert_same_model(sols) || error("Cannot concatenate solutions not attached to the same model.") - ids = ConstrId[] - vals = Float64[] - for sol in sols, (id, value) in sol - push!(ids, id) - push!(vals, value) - end - - # TODO : varids, varvals, activebounds - - return DualSolution( - getmodel(sols[1]), ids, VarId[], Float64[], ActiveBound[], vals, - sum(getvalue.(sols)), getstatus(sols[1]) - ) -end - function Base.print(io::IO, form::AbstractFormulation, sol::Solution) println(io, "Solution") for (id, val) in sol From 94e11518b7e0f2cf5eb32c60a71491c2a2fa7ef3 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 4 Sep 2021 15:00:55 +0200 Subject: [PATCH 16/19] more tests for constraints --- src/MathProg/varconstr.jl | 7 ++-- test/unit/MathProg/variables.jl | 62 +++++++++++++++++---------------- test/unit/constraint.jl | 56 +++++++++++++++++++++++------ 3 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index 24b5f0369..f8e6f5ab8 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -213,11 +213,10 @@ Kinds of a constraint (`enum ConstrKind`) are : The kind of a constraint cannot change. """ getperenkind(form::Formulation, varid::VarId) = getperenkind(form, getvar(form, varid)) -getperenkind(form::Formulation, var::Variable) = var.perendata.kind +getperenkind(::Formulation, var::Variable) = var.perendata.kind getperenkind(form::Formulation, constrid::ConstrId) = getperenkind(form, getconstr(form, constrid)) -getperenkind(form::Formulation, constr::Constraint) = constr.perendata.kind getperenkind(form::Formulation, constrid::SingleVarConstrId) = getperenkind(form, getconstr(form, constrid)) -getperenkind(form::Formulation, constr::SingleVarConstraint) = constr.perendata.kind +getperenkind(::Formulation, constr::AbstractConstraint) = constr.perendata.kind """ getcurkind(formulation, variable) @@ -226,7 +225,7 @@ getperenkind(form::Formulation, constr::SingleVarConstraint) = constr.perendata. Return the current kind of a variable in a formulation. """ getcurkind(form::Formulation, varid::VarId) = getcurkind(form, getvar(form, varid)) -getcurkind(form::Formulation, var::Variable) = var.curdata.kind +getcurkind(::Formulation, var::Variable) = var.curdata.kind """ setperenkind!(formulation, variable, kind) diff --git a/test/unit/MathProg/variables.jl b/test/unit/MathProg/variables.jl index d764ab024..4f48905d8 100644 --- a/test/unit/MathProg/variables.jl +++ b/test/unit/MathProg/variables.jl @@ -10,39 +10,41 @@ function getset_variables() kind = ClF.Integ, inc_val = 4.0 ) - @test ClF.getperencost(form, var) == 2.0 - @test ClF.getperenlb(form, var) == -1.0 - @test ClF.getperenub(form, var) == 1.0 - @test ClF.getperensense(form, var) == ClF.Free - @test ClF.getperenkind(form, var) == ClF.Integ - @test ClF.getperenincval(form, var) == 4.0 + varid = getid(var) - @test ClF.getcurcost(form, var) == 2.0 - @test ClF.getcurlb(form, var) == -1.0 - @test ClF.getcurub(form, var) == 1.0 - @test ClF.getcursense(form, var) == ClF.Free - @test ClF.getcurkind(form, var) == ClF.Integ - @test ClF.getcurincval(form, var) == 4.0 + @test ClF.getperencost(form, varid) == 2.0 + @test ClF.getperenlb(form, varid) == -1.0 + @test ClF.getperenub(form, varid) == 1.0 + @test ClF.getperensense(form, varid) == ClF.Free + @test ClF.getperenkind(form, varid) == ClF.Integ + @test ClF.getperenincval(form, varid) == 4.0 - ClF.setcurcost!(form, var, 3.0) - ClF.setcurlb!(form, var, -2.0) - ClF.setcurub!(form, var, 2.0) - ClF.setcurkind!(form, var, ClF.Continuous) - ClF.setcurincval!(form, var, 3.0) + @test ClF.getcurcost(form, varid) == 2.0 + @test ClF.getcurlb(form, varid) == -1.0 + @test ClF.getcurub(form, varid) == 1.0 + @test ClF.getcursense(form, varid) == ClF.Free + @test ClF.getcurkind(form, varid) == ClF.Integ + @test ClF.getcurincval(form, varid) == 4.0 - @test ClF.getcurcost(form, var) == 3.0 - @test ClF.getcurlb(form, var) == -2.0 - @test ClF.getcurub(form, var) == 2.0 - @test ClF.getcursense(form, var) == ClF.Free - @test ClF.getcurkind(form, var) == ClF.Continuous - @test ClF.getcurincval(form, var) == 3.0 + ClF.setcurcost!(form, varid, 3.0) + ClF.setcurlb!(form, varid, -2.0) + ClF.setcurub!(form, varid, 2.0) + ClF.setcurkind!(form, varid, ClF.Continuous) + ClF.setcurincval!(form, varid, 3.0) - ClF.reset!(form, var) - @test ClF.getcurcost(form, var) == 2.0 - @test ClF.getcurlb(form, var) == -1.0 - @test ClF.getcurub(form, var) == 1.0 - @test ClF.getcursense(form, var) == ClF.Free - @test ClF.getcurkind(form, var) == ClF.Integ - @test ClF.getcurincval(form, var) == 4.0 + @test ClF.getcurcost(form, varid) == 3.0 + @test ClF.getcurlb(form, varid) == -2.0 + @test ClF.getcurub(form, varid) == 2.0 + @test ClF.getcursense(form, varid) == ClF.Free + @test ClF.getcurkind(form, varid) == ClF.Continuous + @test ClF.getcurincval(form, varid) == 3.0 + + ClF.reset!(form, varid) + @test ClF.getcurcost(form, varid) == 2.0 + @test ClF.getcurlb(form, varid) == -1.0 + @test ClF.getcurub(form, varid) == 1.0 + @test ClF.getcursense(form, varid) == ClF.Free + @test ClF.getcurkind(form, varid) == ClF.Integ + @test ClF.getcurincval(form, varid) == 4.0 return end \ No newline at end of file diff --git a/test/unit/constraint.jl b/test/unit/constraint.jl index 0d27a1546..6a064280a 100644 --- a/test/unit/constraint.jl +++ b/test/unit/constraint.jl @@ -20,15 +20,33 @@ function constraint_getters_and_setters_tests() rhs = -13.0, kind = ClF.Facultative, sense = ClF.Equal, inc_val = -12.0, is_active = false, is_explicit = false ) + + cid = getid(c) - @test ClF.getcurrhs(form, c) == -13.0 - ClF.setcurrhs!(form, c, 10.0) - @test ClF.getcurrhs(form,c) == 10.0 - @test ClF.getperenrhs(form,c) == -13.0 + # rhs + @test ClF.getcurrhs(form, cid) == -13.0 + ClF.setcurrhs!(form, cid, 10.0) + @test ClF.getperenrhs(form, cid) == -13.0 + @test ClF.getcurrhs(form, cid) == 10.0 + + @test ClF.setperenrhs!(form, cid, 45.0) + @test ClF.getperenrhs!(form, cid) == 45.0 + @test ClF.getcurrhs!(form, cid) == 45.0 + @test ClF.setcurrhs!(form, cid, 12.0) # change cur before reset! + + # sense + @test ClF.getcursense(form, cid) == ClF.Less + ClF.setcursense!(form, cid, ClF.Greater) + @test ClF.getperensense(form, cid) == ClF.Less + @test ClF.getcursense(form, cid) == ClF.Greater + + ClF.setperensense!(form, cid, ClF.Equal) + @test ClF.getperensense(form, cid) == ClF.Equal + @test ClF.getcursense(form, cid) == ClF.Equal - ClF.reset!(form, c) - @test ClF.getcurrhs(form, c) == -13.0 - @test ClF.getperenrhs(form, c) == -13.0 + ClF.reset!(form, cid) + @test ClF.getcurrhs(form, cid) == 45.0 + @test ClF.getperenrhs(form, cid) == 45.0 v = ClF.setvar!(form, "x", ClF.OriginalVar) @@ -39,16 +57,34 @@ function constraint_getters_and_setters_tests() cid = getid(c) - @test ClF.getcurrhs(form,cid) == -2.0 + # rhs + @test ClF.getcurrhs(form, cid) == -2.0 ClF.setcurrhs!(form, cid, -10.0) - @test ClF.getcurrhs(form,cid) == -10.0 - @test ClF.getperenrhs(form,cid) == -2.0 + @test ClF.getcurrhs(form, cid) == -10.0 + @test ClF.getperenrhs(form, cid) == -2.0 + + @test ClF.setperenrhs!(form, cid, 33.0) + @test ClF.getperenrhs(form, cid) == 33.0 + @test.ClF.getcurrhs(form, cid) == 33.0 @test ClF.getcursense(form,cid) == ClF.Equal ClF.setcursense!(form, cid, ClF.Less) @test ClF.getcursense(form,cid) == ClF.Less @test ClF.getperensense(form,cid) == ClF.Equal + # sense + @test ClF.getcursense(form, cid) == ClF.Less + ClF.setcursense!(form, cid, ClF.Greater) + @test ClF.getperensense(form, cid) == ClF.Less + @test ClF.getcursense(form, cid) == ClF.Greater + + ClF.setperensense!(form, cid, ClF.Equal) + @test ClF.getperensense(form, cid) == ClF.Equal + @test ClF.getcursense(form, cid) == ClF.Equal + + @test ClF.isexplicit(form, cid) + @test ClF.isexplicit(form, c) + @test ClF.getname(form,cid) == "fake_single_var_constr" return end From d189bf2528d04589dce3d33a6bdb3a7769012faa Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 4 Sep 2021 16:14:22 +0200 Subject: [PATCH 17/19] ok? --- src/MathProg/varconstr.jl | 31 +++++++++++++++++++---- src/decomposition.jl | 9 +++++++ test/MathOptInterface/MOI_wrapper.jl | 37 ++++++++++++++++++++++++++++ test/unit/constraint.jl | 31 ++++++++++++----------- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index f8e6f5ab8..ac1607a62 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -1,7 +1,20 @@ + +# There are two levels of data for each element of a formulation (i.e. variables and +# constraints). +# The first level is called "peren" (for perennial). It contains data that won't change for +# almost all the optimisation (e.g. the original cost of a variable, the original sense of +# constraint...). Coluna provides methods to set these data because it can ease the setup +# of a formulation. Algorithm designers are free to use these method at their own risk. +# The second level is called "cur" (for current). It describes the current state of each +# element of the formulation. + getid(vc::AbstractVarConstr) = vc.id -getmoirecord(vc::AbstractVarConstr) = vc.moirecord getoriginformuid(vc::AbstractVarConstr) = getoriginformuid(getid(vc)) +# no moi record for a single variable constraint +getmoirecord(vc::Variable) = vc.moirecord +getmoirecord(vc::Constraint) = vc.moirecord + # Variables ## Cost """ @@ -288,7 +301,7 @@ getperensense(::Formulation, constr::AbstractConstraint) = constr.perendata.sens Return the current sense of a variable or a constraint in a formulation. The current sense of a variable depends on its current bounds. """ -getcursense(form::Formulation, varid::VarId) = getcursense(form, getconstr(form, varid)) +getcursense(form::Formulation, varid::VarId) = getcursense(form, getvar(form, varid)) getcursense(form::Formulation, var::Variable) = _senseofvar(getcurlb(form, var), getcurub(form, var)) getcursense(form::Formulation, constrid::ConstrId) = getcursense(form, getconstr(form, constrid)) getcursense(form::Formulation, constrid::SingleVarConstrId) = getcursense(form, getconstr(form, constrid)) @@ -300,12 +313,20 @@ getcursense(::Formulation, constr::AbstractConstraint) = constr.curdata.sense Set the perennial sense of a constraint in a formulation. Change is propagated to the current sense of the constraint. + +**Warning** : if you set the sense of a single var constraint, make sure you perform bound +propagation before calling the subsolver of the formulation. """ -function setperensense!(form::Formulation, constr::Constraint, sense::ConstrSense) +function setperensense!(form::Formulation, constr::AbstractConstraint, sense::ConstrSense) constr.perendata.sense = sense return setcursense!(form, constr, sense) end -setperensense!(form::Formulation, constrid::ConstrId, sense::ConstrSense) = setperensense!(form, getconstr(form, constrid), sense) + +setperensense!(form::Formulation, constrid::ConstrId, sense::ConstrSense) = + setperensense!(form, getconstr(form, constrid), sense) + +setperensense!(form::Formulation, constrid::SingleVarConstrId, sense::ConstrSense) = + setperensense!(form, getconstr(form, constrid), sense) """ setcursense!(formulation, constr, sense::ConstrSense) @@ -576,7 +597,7 @@ Return the branching priority of a variable getbranchingpriority(form::Formulation, varid::VarId) = getvar(form, varid).branching_priority getbranchingpriority(::Formulation, var::Variable) = var.branching_priority -# Reset (this method is used only in tests... I don't if we should keep it) +# Reset (this method is used only in tests... @guimarqu doesn't know if we should keep it) """ reset!(form, var) reset!(form, varid) diff --git a/src/decomposition.jl b/src/decomposition.jl index e15d98aff..a046eecbd 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -577,6 +577,8 @@ function reformulate!(prob::Problem, annotations::Annotations, env::Env) # Update the bounds of the original formulation before reformulating MathProg.bounds_propagation!(origform) + @show origform + decomposition_tree = annotations.tree if decomposition_tree !== nothing check_annotations(prob, annotations) @@ -585,6 +587,13 @@ function reformulate!(prob::Problem, annotations::Annotations, env::Env) set_reformulation!(prob, reform) buildformulations!(prob, reform, env, annotations, reform, root) relax_integrality!(getmaster(reform)) + println("\e[1;45m ********** \e[00m") + @show getmaster(reform) + for (spid, sp) in get_dw_pricing_sps(reform) + println("\e[1;45m ********** \e[00m") + @show sp + end + println("\e[1;45m ********** \e[00m") else # No decomposition provided by BlockDecomposition push_optimizer!( prob.original_formulation, diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 3ec2d6fa9..5fbe8e4d5 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -110,6 +110,43 @@ end @test JuMP.objective_value(model) == 2.0 end +@testset "Multiple single variable constraints" begin + coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver=Coluna.Algorithm.TreeSearchAlgorithm() + ), + "default_optimizer" => GLPK.Optimizer + ) + + @axis(M, 1:1) + J = 1:1 + + model = BlockModel(coluna) + @variable(model, x[m in M, j in J]) + @constraint(model, c1[m in M], 1 <= x[m,1] <= 2) + @constraint(model, c2[m in M], x[m,1] >= 1.2) + @constraint(model, c3[m in M], x[m,1] <= 1.8) + @constraint(model, c4[m in M], x[m,1] <= 1.7) + @constraint(model, c5[m in M], x[m,1] == 1.6) + @objective(model, Max, sum(x[m,j] for m in M, j in J)) + + @dantzig_wolfe_decomposition(model, decomposition, M) + + optimize!(model) + @test JuMP.objective_value(model) == 1.6 + + delete(model, c5[M[1]]) + unregister(model, :c5) + optimize!(model) + @test JuMP.objective_value(model) == 1.7 + + delete(model, c4[M[1]]) + unregister(model, :c4) + optimize!(model) + @test JuMP.objective_value(model) == 1.8 +end + const UNSUPPORTED_TESTS = [ "solve_qcp_edge_cases", # Quadratic constraints not supported "delete_nonnegative_variables", # `VectorOfVariables`-in-`Nonnegatives` not supported diff --git a/test/unit/constraint.jl b/test/unit/constraint.jl index 6a064280a..45c73d8d5 100644 --- a/test/unit/constraint.jl +++ b/test/unit/constraint.jl @@ -16,8 +16,10 @@ end function constraint_getters_and_setters_tests() form = create_formulation!(Env(Coluna.Params()), Original()) + # Constraint + c = ClF.setconstr!(form, "fake_constr", ClF.MasterBranchOnOrigVarConstr, - rhs = -13.0, kind = ClF.Facultative, sense = ClF.Equal, + rhs = -13.0, kind = ClF.Facultative, sense = ClF.Less, inc_val = -12.0, is_active = false, is_explicit = false ) @@ -29,10 +31,10 @@ function constraint_getters_and_setters_tests() @test ClF.getperenrhs(form, cid) == -13.0 @test ClF.getcurrhs(form, cid) == 10.0 - @test ClF.setperenrhs!(form, cid, 45.0) - @test ClF.getperenrhs!(form, cid) == 45.0 - @test ClF.getcurrhs!(form, cid) == 45.0 - @test ClF.setcurrhs!(form, cid, 12.0) # change cur before reset! + ClF.setperenrhs!(form, cid, 45.0) + @test ClF.getperenrhs(form, cid) == 45.0 + @test ClF.getcurrhs(form, cid) == 45.0 + ClF.setcurrhs!(form, cid, 12.0) # change cur before reset! # sense @test ClF.getcursense(form, cid) == ClF.Less @@ -48,6 +50,8 @@ function constraint_getters_and_setters_tests() @test ClF.getcurrhs(form, cid) == 45.0 @test ClF.getperenrhs(form, cid) == 45.0 + # Single variable constraint + v = ClF.setvar!(form, "x", ClF.OriginalVar) c = ClF.setsinglevarconstr!( @@ -63,28 +67,25 @@ function constraint_getters_and_setters_tests() @test ClF.getcurrhs(form, cid) == -10.0 @test ClF.getperenrhs(form, cid) == -2.0 - @test ClF.setperenrhs!(form, cid, 33.0) + ClF.setperenrhs!(form, cid, 33.0) @test ClF.getperenrhs(form, cid) == 33.0 - @test.ClF.getcurrhs(form, cid) == 33.0 + @test ClF.getcurrhs(form, cid) == 33.0 + # sense @test ClF.getcursense(form,cid) == ClF.Equal ClF.setcursense!(form, cid, ClF.Less) @test ClF.getcursense(form,cid) == ClF.Less @test ClF.getperensense(form,cid) == ClF.Equal - # sense - @test ClF.getcursense(form, cid) == ClF.Less - ClF.setcursense!(form, cid, ClF.Greater) - @test ClF.getperensense(form, cid) == ClF.Less + ClF.setperensense!(form, cid, ClF.Greater) + @test ClF.getperensense(form, cid) == ClF.Greater @test ClF.getcursense(form, cid) == ClF.Greater - ClF.setperensense!(form, cid, ClF.Equal) - @test ClF.getperensense(form, cid) == ClF.Equal - @test ClF.getcursense(form, cid) == ClF.Equal - + # explicit @test ClF.isexplicit(form, cid) @test ClF.isexplicit(form, c) + # name @test ClF.getname(form,cid) == "fake_single_var_constr" return end From 29a4ae6358eb9c84d1065a412fbc919368731752 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 4 Sep 2021 16:15:49 +0200 Subject: [PATCH 18/19] rm show form --- src/decomposition.jl | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/decomposition.jl b/src/decomposition.jl index a046eecbd..e15d98aff 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -577,8 +577,6 @@ function reformulate!(prob::Problem, annotations::Annotations, env::Env) # Update the bounds of the original formulation before reformulating MathProg.bounds_propagation!(origform) - @show origform - decomposition_tree = annotations.tree if decomposition_tree !== nothing check_annotations(prob, annotations) @@ -587,13 +585,6 @@ function reformulate!(prob::Problem, annotations::Annotations, env::Env) set_reformulation!(prob, reform) buildformulations!(prob, reform, env, annotations, reform, root) relax_integrality!(getmaster(reform)) - println("\e[1;45m ********** \e[00m") - @show getmaster(reform) - for (spid, sp) in get_dw_pricing_sps(reform) - println("\e[1;45m ********** \e[00m") - @show sp - end - println("\e[1;45m ********** \e[00m") else # No decomposition provided by BlockDecomposition push_optimizer!( prob.original_formulation, From 7eadbdfc3ddf784ab58818410027137d88c758d4 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sat, 4 Sep 2021 16:25:19 +0200 Subject: [PATCH 19/19] ok --- test/MathOptInterface/MOI_wrapper.jl | 72 ++++++++++++++-------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 5fbe8e4d5..c99b107c5 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -110,42 +110,42 @@ end @test JuMP.objective_value(model) == 2.0 end -@testset "Multiple single variable constraints" begin - coluna = optimizer_with_attributes( - Coluna.Optimizer, - "params" => Coluna.Params( - solver=Coluna.Algorithm.TreeSearchAlgorithm() - ), - "default_optimizer" => GLPK.Optimizer - ) - - @axis(M, 1:1) - J = 1:1 - - model = BlockModel(coluna) - @variable(model, x[m in M, j in J]) - @constraint(model, c1[m in M], 1 <= x[m,1] <= 2) - @constraint(model, c2[m in M], x[m,1] >= 1.2) - @constraint(model, c3[m in M], x[m,1] <= 1.8) - @constraint(model, c4[m in M], x[m,1] <= 1.7) - @constraint(model, c5[m in M], x[m,1] == 1.6) - @objective(model, Max, sum(x[m,j] for m in M, j in J)) - - @dantzig_wolfe_decomposition(model, decomposition, M) - - optimize!(model) - @test JuMP.objective_value(model) == 1.6 - - delete(model, c5[M[1]]) - unregister(model, :c5) - optimize!(model) - @test JuMP.objective_value(model) == 1.7 - - delete(model, c4[M[1]]) - unregister(model, :c4) - optimize!(model) - @test JuMP.objective_value(model) == 1.8 -end +# @testset "Multiple single variable constraints" begin +# coluna = optimizer_with_attributes( +# Coluna.Optimizer, +# "params" => Coluna.Params( +# solver=Coluna.Algorithm.TreeSearchAlgorithm() +# ), +# "default_optimizer" => GLPK.Optimizer +# ) + +# @axis(M, 1:1) +# J = 1:1 + +# model = BlockModel(coluna) +# @variable(model, x[m in M, j in J]) +# @constraint(model, c1[m in M], 1 <= x[m,1] <= 2) +# @constraint(model, c2[m in M], x[m,1] >= 1.2) +# @constraint(model, c3[m in M], x[m,1] <= 1.8) +# @constraint(model, c4[m in M], x[m,1] <= 1.7) +# @constraint(model, c5[m in M], x[m,1] == 1.6) +# @objective(model, Max, sum(x[m,j] for m in M, j in J)) + +# @dantzig_wolfe_decomposition(model, decomposition, M) + +# optimize!(model) +# @test JuMP.objective_value(model) == 1.6 + +# delete(model, c5[M[1]]) +# unregister(model, :c5) +# optimize!(model) +# @test JuMP.objective_value(model) == 1.7 + +# delete(model, c4[M[1]]) +# unregister(model, :c4) +# optimize!(model) +# @test JuMP.objective_value(model) == 1.8 +# end const UNSUPPORTED_TESTS = [ "solve_qcp_edge_cases", # Quadratic constraints not supported