Skip to content

Commit

Permalink
Correction in the column generation integrality check: continuous var…
Browse files Browse the repository at this point in the history
…iables should be ignored (#1112)
  • Loading branch information
rrsadykov authored Nov 30, 2023
1 parent feac4fc commit 54cc8aa
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 45 deletions.
44 changes: 28 additions & 16 deletions src/MathProg/projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ projection_is_possible(form) = false

projection_is_possible(master::Formulation{DwMaster}) = true

Base.isless(A::DynamicMatrixColView{VarId, VarId, Float64}, B::DynamicMatrixColView{VarId, VarId, Float64}) = cmp(A, B) < 0
Base.isless(A::DynamicMatrixColView{VarId,VarId,Float64}, B::DynamicMatrixColView{VarId,VarId,Float64}) = cmp(A, B) < 0

function Base.cmp(A::DynamicMatrixColView{VarId, VarId, Float64}, B::DynamicMatrixColView{VarId, VarId, Float64})
function Base.cmp(A::DynamicMatrixColView{VarId,VarId,Float64}, B::DynamicMatrixColView{VarId,VarId,Float64})
for (a, b) in zip(A, B)
if !isequal(a, b)
return isless(a, b) ? -1 : 1
Expand Down Expand Up @@ -39,17 +39,17 @@ _new_set_of_rolls(::Type{Vector{E}}) where {E} = Vector{Float64}[]
_new_roll(::Type{Vector{E}}, col_len) where {E} = zeros(Float64, col_len)
_roll_is_integer(roll::Vector{Float64}) = all(map(r -> abs(r - round(r)) <= Coluna.DEF_OPTIMALITY_ATOL, roll))

_new_set_of_rolls(::Type{DynamicMatrixColView{VarId, VarId, Float64}}) = Dict{VarId, Float64}[]
_new_roll(::Type{DynamicMatrixColView{VarId, VarId, Float64}}, _) = Dict{VarId, Float64}()
_roll_is_integer(roll::Dict{VarId, Float64}) = all(map(r -> abs(r - round(r)) <= Coluna.DEF_OPTIMALITY_ATOL, values(roll)))
_new_set_of_rolls(::Type{DynamicMatrixColView{VarId,VarId,Float64}}) = Dict{VarId,Float64}[]
_new_roll(::Type{DynamicMatrixColView{VarId,VarId,Float64}}, _) = Dict{VarId,Float64}()
_roll_is_integer(roll::Dict{VarId,Float64}) = all(map(r -> abs(r - round(r)) <= Coluna.DEF_OPTIMALITY_ATOL, values(roll)))

function _mapping(columns::Vector{A}, values::Vector{B}; col_len::Int = 10) where {A,B}
function _mapping(columns::Vector{A}, values::Vector{B}; col_len::Int=10) where {A,B}
p = sortperm(columns, rev=true)
columns = columns[p]
values = values[p]

rolls = _new_set_of_rolls(eltype(columns))
total_width_assigned = 0
total_width_assigned = 0
nb_roll_opened = 1 # roll is width 1
cur_roll = _new_roll(eltype(columns), col_len)

Expand All @@ -70,18 +70,28 @@ function _mapping(columns::Vector{A}, values::Vector{B}; col_len::Int = 10) wher
return rolls
end

function _mapping_by_subproblem(columns::Dict{Int, Vector{A}}, values::Dict{Int, Vector{B}}) where {A,B}
function _mapping_by_subproblem(columns::Dict{Int,Vector{A}}, values::Dict{Int,Vector{B}}) where {A,B}
return Dict(
uid => _mapping(cols, values[uid]) for (uid, cols) in columns
uid => _mapping(cols, values[uid]) for (uid, cols) in columns
)
end

_rolls_are_integer(rolls) = all(_roll_is_integer.(rolls))
_subproblem_rolls_are_integer(rolls_by_sp::Dict) = all(_rolls_are_integer.(values(rolls_by_sp)))

# removes information about continuous variables from rolls, as this information should be ignored when checking integrality
function _remove_continuous_vars_from_rolls!(rolls_by_sp::Dict, reform::Reformulation)
for (uid, rolls) in rolls_by_sp
spform = get_dw_pricing_sps(reform)[uid]
for roll in rolls
filter!(pair -> getcurkind(spform, pair.first) != Continuous, roll)
end
end
end

function _extract_data_for_mapping(sol::PrimalSolution{Formulation{DwMaster}})
columns = Dict{Int, Vector{DynamicMatrixColView{VarId, VarId, Float64}}}()
values = Dict{Int, Vector{Float64}}()
columns = Dict{Int,Vector{DynamicMatrixColView{VarId,VarId,Float64}}}()
values = Dict{Int,Vector{Float64}}()
master = getmodel(sol)
reform = getparent(master)
if isnothing(reform)
Expand All @@ -97,9 +107,9 @@ function _extract_data_for_mapping(sol::PrimalSolution{Formulation{DwMaster}})
if isnothing(spform)
error("Projection: cannot retrieve Dantzig-Wolfe pricing subproblem with uid $origin_form_uid")
end
column = @view get_primal_sol_pool(spform).solutions[varid,:]
column = @view get_primal_sol_pool(spform).solutions[varid, :]
if !haskey(columns, origin_form_uid)
columns[origin_form_uid] = DynamicMatrixColView{VarId, VarId, Float64}[]
columns[origin_form_uid] = DynamicMatrixColView{VarId,VarId,Float64}[]
values[origin_form_uid] = Float64[]
end
push!(columns[origin_form_uid], column)
Expand All @@ -125,8 +135,8 @@ function _proj_cols_on_rep(sol::PrimalSolution{Formulation{DwMaster}}, extracted
for spid in keys(extracted_cols)
for (column, val) in Iterators.zip(extracted_cols[spid], extracted_vals[spid])
for (repid, repval) in column
if getduty(repid) <= DwSpPricingVar || getduty(repid) <= DwSpSetupVar ||
getduty(repid) <= MasterRepPricingVar || getduty(repid) <= MasterRepPricingSetupVar
if getduty(repid) <= DwSpPricingVar || getduty(repid) <= DwSpSetupVar ||
getduty(repid) <= MasterRepPricingVar || getduty(repid) <= MasterRepPricingSetupVar
mastrepvar = getvar(master, repid)
@assert !isnothing(mastrepvar)
mastrepid = getid(mastrepvar)
Expand All @@ -149,12 +159,14 @@ function proj_cols_is_integer(sol::PrimalSolution{Formulation{DwMaster}})
columns, values = _extract_data_for_mapping(sol)
projected_sol = _proj_cols_on_rep(sol, columns, values)
rolls = _mapping_by_subproblem(columns, values)
reform = getparent(getmodel(sol))
_remove_continuous_vars_from_rolls!(rolls, reform)
integer_rolls = _subproblem_rolls_are_integer(rolls)
return isinteger(projected_sol) && integer_rolls
end

############################################################################################
# Porjection of Benders master on original formulation.
# Projection of Benders master on original formulation.
############################################################################################

projection_is_possible(master::Formulation{BendersMaster}) = false
Expand Down
64 changes: 35 additions & 29 deletions test/unit/MathProg/projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function test_mapping_operator_1()

v = Float64[3, 2, 0, 0]

result = Coluna.MathProg._mapping(G, v; col_len = 7)#, 6)
result = Coluna.MathProg._mapping(G, v; col_len=7)#, 6)
@test result == [
[1, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 1, 1, 0, 1],
Expand Down Expand Up @@ -52,7 +52,7 @@ function test_mapping_operator_2()

v = Float64[0, 1/2, 1, 1/2, 0, 0, 1, 1, 0, 0, 1/2, 0, 1/2, 0, 0]

result = Coluna.MathProg._mapping(G, v; col_len = 4)
result = Coluna.MathProg._mapping(G, v; col_len=4)
@test result == [
[1.0, 1.0, 0.0, 0.5],
[1.0, 0.5, 0.5, 0.5],
Expand Down Expand Up @@ -96,7 +96,7 @@ function identical_subproblems_vrp()
form = """
master
min
x_12 + x_13 + x_14 + x_23 + x_24 + x_34 + MC1 + MC2 + MC3 + MC4 + 0.0 PricingSetupVar_sp_5
x_12 + x_13 + x_14 + x_23 + x_24 + x_34 + s_1 + 1.4 MC1 + 1.4 MC2 + 1.4 MC3 + 1.4 MC4 + 0.0 PricingSetupVar_sp_5
s.t.
x_12 + x_13 + x_14 + MC1 + MC3 + MC4 >= 1.0
x_12 + x_23 + x_24 + MC1 + MC2 + MC4 >= 1.0
Expand All @@ -107,13 +107,15 @@ function identical_subproblems_vrp()
dw_sp
min
x_12 + x_13 + x_14 + x_23 + x_24 + x_34 + 0.0 PricingSetupVar_sp_5
x_12 + x_13 + x_14 + x_23 + x_24 + x_34 + s_1 + 0.0 PricingSetupVar_sp_5
s.t.
x_12 + x_13 + x_14 + x_23 + x_24 + x_34 >= 0
continuous
columns
MC1, MC2, MC3, MC4
representatives
s_1
integer
pricing_setup
Expand All @@ -122,14 +124,16 @@ function identical_subproblems_vrp()
binary
representatives
x_12, x_13, x_14, x_23, x_24, x_34
bounds
0.0 <= x_12 <= 1.0
0.0 <= x_13 <= 1.0
0.0 <= x_14 <= 1.0
0.0 <= x_23 <= 1.0
0.0 <= x_24 <= 1.0
0.0 <= x_34 <= 1.0
-Inf <= s_1 <= Inf
MC1 >= 0
MC2 >= 0
MC3 >= 0
Expand All @@ -148,35 +152,36 @@ function projection_from_dw_reform_to_master_1()
spform = first(sps)
pool = ClMP.get_primal_sol_pool(spform)

var_ids = map(n -> ClMP.getid(ClMP.getvar(spform, mastervarids[n])), ["x_12", "x_13", "x_14", "x_23", "x_24", "x_34"])
var_ids = map(n -> ClMP.getid(ClMP.getvar(spform, mastervarids[n])), ["x_12", "x_13", "x_14", "x_23", "x_24", "x_34", "s_1"])

# VarId[Variableu2, Variableu1, Variableu3, Variableu4, Variableu5, Variableu6]
for (name, vals) in Iterators.zip(
["MC1", "MC2", "MC3", "MC4"],
[
[1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0, 1.0],
[0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
[1.0, 0.0, 1.0, 0.0, 0.0, 0.0]
]
["MC1", "MC2", "MC3", "MC4"],
[
[1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.4],
[0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.4],
[0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.4],
[1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.4]
]
)
col_id = ClMP.VarId(mastervarids[name]; duty = DwSpPrimalSol)
col_id = ClMP.VarId(mastervarids[name]; duty=DwSpPrimalSol)
ClMP.push_in_pool!(pool, ClMP.PrimalSolution(spform, var_ids, vals, 1.0, ClMP.FEASIBLE_SOL), col_id, 1.0)
end

# Create primal solution where each route is used 1/2 time.
# This solution is integer feasible.
solution = Coluna.MathProg.PrimalSolution(
master,
map(n -> ClMP.VarId(mastervarids[n]; origin_form_uid = 4), ["MC1", "MC2", "MC3", "MC4"]),
[1/2, 1/2, 1/2, 1/2],
map(n -> ClMP.VarId(mastervarids[n]; origin_form_uid=4), ["MC1", "MC2", "MC3", "MC4"]),
[1 / 2, 1 / 2, 1 / 2, 1 / 2],
2.0,
ClB.FEASIBLE_SOL
)

# Test integration
columns, values = Coluna.MathProg._extract_data_for_mapping(solution)
rolls = Coluna.MathProg._mapping_by_subproblem(columns, values)
Coluna.MathProg._remove_continuous_vars_from_rolls!(rolls, reform)

# Expected:
# | 1/2 of [1.0, 0.0, 1.0, 0.0, 0.0, 0.0]
Expand All @@ -186,7 +191,7 @@ function projection_from_dw_reform_to_master_1()
# | 1/2 of [0.0, 0.0, 0.0, 1.0, 0.0, 1.0]
# -----> [0.0, 0.0, 0.5, 0.5, 0.0, 1.0]

@test rolls == Dict( 4 => [
@test rolls == Dict(4 => [
Dict(mastervarids["x_14"] => 0.5, mastervarids["x_23"] => 0.5, mastervarids["x_34"] => 1.0)
Dict(mastervarids["x_12"] => 1.0, mastervarids["x_14"] => 0.5, mastervarids["x_23"] => 0.5)
])
Expand All @@ -210,25 +215,25 @@ function projection_from_dw_reform_to_master_2()
spform = first(sps)
pool = ClMP.get_primal_sol_pool(spform)

var_ids = map(n -> ClMP.getid(ClMP.getvar(spform, mastervarids[n])), ["x_12", "x_13", "x_14", "x_23", "x_24", "x_34"])
var_ids = map(n -> ClMP.getid(ClMP.getvar(spform, mastervarids[n])), ["x_12", "x_13", "x_14", "x_23", "x_24", "x_34", "s_1"])

# VarId[Variableu2, Variableu1, Variableu3, Variableu4, Variableu5, Variableu6]
for (name, vals) in Iterators.zip(
["MC1", "MC2"],
[
[0.9999999, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.9999999],
]
["MC1", "MC2"],
[
[0.9999999, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.9999999, 0.7],
]
)
col_id = ClMP.VarId(mastervarids[name]; duty = DwSpPrimalSol)
col_id = ClMP.VarId(mastervarids[name]; duty=DwSpPrimalSol)
ClMP.push_in_pool!(pool, ClMP.PrimalSolution(spform, var_ids, vals, 1.0, ClMP.FEASIBLE_SOL), col_id, 1.0)
end

# Create primal solution where each route is used 1/2 time.
# This solution is integer feasible.
solution = Coluna.MathProg.PrimalSolution(
master,
map(n -> ClMP.VarId(mastervarids[n]; origin_form_uid = 4), ["MC1", "MC2"]),
map(n -> ClMP.VarId(mastervarids[n]; origin_form_uid=4), ["MC1", "MC2"]),
[1.0, 1.0],
2.0,
ClB.FEASIBLE_SOL
Expand All @@ -237,12 +242,13 @@ function projection_from_dw_reform_to_master_2()
# Test integration
columns, values = Coluna.MathProg._extract_data_for_mapping(solution)
rolls = Coluna.MathProg._mapping_by_subproblem(columns, values)
Coluna.MathProg._remove_continuous_vars_from_rolls!(rolls, reform)

# Expected:
# | 1 of [0.9999999, 0.0, 0.0, 0.0, 0.0, 0.0]
# | 1 of [0.0, 0.0, 0.0, 0.0, 0.0, 0.9999999]

@test rolls == Dict( 4 => [
@test rolls == Dict(4 => [
Dict(mastervarids["x_34"] => 0.9999999),
Dict(mastervarids["x_12"] => 0.9999999)
])
Expand All @@ -251,6 +257,6 @@ function projection_from_dw_reform_to_master_2()
@test proj[mastervarids["x_12"]] == 0.9999999
@test proj[mastervarids["x_34"]] == 0.9999999

@test Coluna.MathProg.proj_cols_is_integer(solution) == true
@test Coluna.MathProg.proj_cols_is_integer(solution) == true
end
register!(unit_tests, "projection", projection_from_dw_reform_to_master_2)

0 comments on commit 54cc8aa

Please sign in to comment.