Skip to content

Commit

Permalink
Pool struct for primal solutions of colgen (#915)
Browse files Browse the repository at this point in the history
  • Loading branch information
guimarqu authored May 29, 2023
1 parent d4338da commit b5ab51d
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/MOIwrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,7 @@ function BD.value(info::ColumnInfo, index::MOI.VariableIndex)
varid = info.optimizer.env.varids[index]
origin_form_uid = getoriginformuid(info.column_var_id)
spform = get_dw_pricing_sps(info.optimizer.inner.re_formulation)[origin_form_uid]
return get_primal_sol_pool(spform)[info.column_var_id,varid]
return get_primal_sol_pool(spform).solutions[info.column_var_id,varid]
end

function MOI.get(model::Optimizer, ::MOI.NumberOfVariables)
Expand Down
1 change: 1 addition & 0 deletions src/MathProg/MathProg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include("bounds.jl")
include("solutions.jl")
include("buffer.jl")
include("manager.jl")
include("pool.jl")
include("duties.jl")
include("formulation.jl")
include("varconstr.jl")
Expand Down
15 changes: 2 additions & 13 deletions src/MathProg/duties.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,15 @@ mutable struct DwSp <: AbstractSpDuty
column_var_kind::VarKind

# Pool of solutions to the Dantzig-Wolfe subproblem.
## Coluna representation of solutions (filtered by `_sol_repr_for_pool`).
## [colid, varid] = value
primalsols_pool::VarVarMatrix
# Hash table to quickly find identical solutions
hashtable_primalsols_pool::HashTable{VarId,VarId}
## Perennial cost of solutions
costs_primalsols_pool::Dict{VarId, Float64}
## Custom representation of solutions
custom_primalsols_pool::Dict{VarId, BD.AbstractCustomData}
pool::Pool
end

"A pricing subproblem of a formulation decomposed using Dantzig-Wolfe."
function DwSp(setup_var, lower_multiplicity_constr_id, upper_multiplicity_constr_id, column_var_kind)
return DwSp(
setup_var, lower_multiplicity_constr_id, upper_multiplicity_constr_id,
column_var_kind,
dynamicsparse(VarId, VarId, Float64; fill_mode = false),
HashTable{VarId, VarId}(),
Dict{VarId, Float64}(),
Dict{VarId, BD.AbstractCustomData}()
Pool()
)
end
mutable struct BendersSp <: AbstractSpDuty
Expand Down
55 changes: 5 additions & 50 deletions src/MathProg/formulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ function _setrobustmembers!(form::Formulation, constr::Constraint, members::VarM
if getduty(varid) <= MasterRepPricingVar || getduty(varid) <= MasterRepPricingSetupVar
# then for all columns having its own variables
for (_, spform) in get_dw_pricing_sps(form.parent_formulation)
for (col_id, col_coeff) in @view get_primal_sol_pool(spform)[:,varid]
for (col_id, col_coeff) in @view get_primal_sol_pool(spform).solutions[:,varid]
coef_matrix[constrid, col_id] += col_coeff * var_coeff
end
end
Expand Down Expand Up @@ -484,41 +484,7 @@ end
############################################################################################
############################################################################################

get_primal_sol_pool(form::Formulation{DwSp}) = form.duty_data.primalsols_pool
_get_primal_sol_pool_hash_table(form::Formulation{DwSp}) = form.duty_data.hashtable_primalsols_pool

############################################################################################
# Pool of solutions
# We consider that the pool is a dynamic sparse matrix.
############################################################################################

# Returns nothing if there is no identical solutions in pool; the id of the
# identical solution otherwise.
function _get_same_sol_in_pool(pool_sols, pool_hashtable, sol)
sols_with_same_members = getsolids(pool_hashtable, sol)
for existing_sol_id in sols_with_same_members
existing_sol = @view pool_sols[existing_sol_id,:]
if existing_sol == sol
return existing_sol_id
end
end
return nothing
end

# We only keep variables that have certain duty in the representation of the
# solution stored in the pool. The second argument allows us to dispatch because
# filter may change depending on the duty of the formulation.
function _sol_repr_for_pool(primal_sol::PrimalSolution, ::DwSp)
var_ids = VarId[]
vals = Float64[]
for (var_id, val) in primal_sol
if getduty(var_id) <= DwSpSetupVar || getduty(var_id) <= DwSpPricingVar
push!(var_ids, var_id)
push!(vals, val)
end
end
return var_ids, vals
end
get_primal_sol_pool(form::Formulation{DwSp}) = form.duty_data.pool

function initialize_solution_pool!(form::Formulation{DwSp}, initial_columns_callback::Function)
master = getmaster(form)
Expand Down Expand Up @@ -556,8 +522,7 @@ subproblem; `nothing` otherwise.
function get_column_from_pool(primal_sol::PrimalSolution{Formulation{DwSp}})
spform = primal_sol.solution.model
pool = get_primal_sol_pool(spform)
pool_hashtable = _get_primal_sol_pool_hash_table(spform)
return _get_same_sol_in_pool(pool, pool_hashtable, primal_sol)
return get_from_pool(pool, primal_sol)
end

"""
Expand Down Expand Up @@ -588,11 +553,6 @@ function insert_column!(
primal_sol
)

pool = get_primal_sol_pool(spform)
pool_hashtable = _get_primal_sol_pool_hash_table(spform)
costs_pool = spform.duty_data.costs_primalsols_pool
custom_pool = spform.duty_data.custom_primalsols_pool

# Compute coefficient members of the column in the matrix.
members = _col_members(primal_sol, getcoefmatrix(master_form))

Expand All @@ -616,14 +576,9 @@ function insert_column!(

# Store the solution in the pool if asked.
if store_in_sp_pool
pool = get_primal_sol_pool(spform)
col_id = VarId(getid(col); duty = DwSpPrimalSol)
var_ids, vals = _sol_repr_for_pool(primal_sol, spform.duty_data)
addrow!(pool, col_id, var_ids, vals)
costs_pool[col_id] = new_col_peren_cost
if primal_sol.custom_data !== nothing
custom_pool[col_id] = primal_sol.custom_data
end
savesolid!(pool_hashtable, col_id, primal_sol)
push_in_pool!(pool, primal_sol, col_id, new_col_peren_cost)
end
return getid(col)
end
Expand Down
81 changes: 81 additions & 0 deletions src/MathProg/pool.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
struct Pool
solutions::DynamicSparseArrays.DynamicSparseMatrix{VarId,VarId,Float64}
solutions_hash::ColunaBase.HashTable{VarId,VarId}
costs::Dict{VarId,Float64}
custom_data::Dict{VarId,BD.AbstractCustomData}
end

function Pool()
return Pool(
DynamicSparseArrays.dynamicsparse(VarId, VarId, Float64; fill_mode = false),
ColunaBase.HashTable{VarId, VarId}(),
Dict{VarId, Float64}(),
Dict{VarId, BD.AbstractCustomData}()
)
end

# Returns nothing if there is no identical solutions in pool; the id of the
# identical solution otherwise.
function _get_same_sol_in_pool(solutions, hashtable, sol)
sols_with_same_members = ColunaBase.getsolids(hashtable, sol)
for existing_sol_id in sols_with_same_members
existing_sol = @view solutions[existing_sol_id,:]
if existing_sol == sol
return existing_sol_id
end
end
return nothing
end

# We only keep variables that have certain duty in the representation of the
# solution stored in the pool. The second argument allows us to dispatch because
# filter may change depending on the duty of the formulation.
function _sol_repr_for_pool(primal_sol::PrimalSolution)
var_ids = VarId[]
vals = Float64[]
for (var_id, val) in primal_sol
if getduty(var_id) <= DwSpSetupVar || getduty(var_id) <= DwSpPricingVar
push!(var_ids, var_id)
push!(vals, val)
end
end
return var_ids, vals
end

"""
same_custom_data(custom_data1, custom_data2) -> Bool
Returns `true`if the custom data are the same, false otherwise.
"""
same_custom_data(custom_data1, custom_data2) = custom_data1 == custom_data2

function get_from_pool(pool::Pool, solution)
existing_sol_id = _get_same_sol_in_pool(pool.solutions, pool.solutions_hash, solution)
if isnothing(existing_sol_id)
return nothing
end

# When there are non-robust cuts, Coluna has not enough information to identify that two
# columns are identical. The columns may be mapped into the same original variables but
# be internally different, meaning that the coefficients of non-robust cuts to be added
# in the future may differ. This is why we need to check custom data.
custom_data1 = get(pool.custom_data, existing_sol_id, nothing)
custom_data2 = solution.custom_data
if same_custom_data(custom_data1, custom_data2)
return existing_sol_id
end
return nothing
end

function push_in_pool!(pool::Pool, solution, sol_id, cost)
var_ids, vals = _sol_repr_for_pool(solution)
DynamicSparseArrays.addrow!(pool.solutions, sol_id, var_ids, vals)
pool.costs[sol_id] = cost
if !isnothing(solution.custom_data)
pool.custom_data[sol_id] = solution.custom_data
end
ColunaBase.savesolid!(pool.solutions_hash, sol_id, solution)
return true
end


2 changes: 1 addition & 1 deletion src/MathProg/projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function _extract_data_for_mapping(sol::PrimalSolution{Formulation{DwMaster}})
if duty <= MasterCol
origin_form_uid = getoriginformuid(varid)
spform = get_dw_pricing_sps(master.parent_formulation)[origin_form_uid]
column = @view get_primal_sol_pool(spform)[varid,:]
column = @view get_primal_sol_pool(spform).solutions[varid,:]
if !haskey(columns, origin_form_uid)
columns[origin_form_uid] = DynamicMatrixColView{VarId, VarId, Float64}[]
values[origin_form_uid] = Float64[]
Expand Down
11 changes: 4 additions & 7 deletions test/unit/MathProg/projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,10 @@ function projection_from_dw_reform_to_master_1()
# Register column in the pool
spform = first(sps)
pool = ClMP.get_primal_sol_pool(spform)
pool_hashtable = ClMP._get_primal_sol_pool_hash_table(spform)
costs_pool = spform.duty_data.costs_primalsols_pool
custom_pool = spform.duty_data.custom_primalsols_pool

var_ids = map(n -> 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"])

@show spform
# VarId[Variableu2, Variableu1, Variableu3, Variableu4, Variableu5, Variableu6]
for (name, vals) in Iterators.zip(
["MC1", "MC2", "MC3", "MC4"],
Expand All @@ -163,9 +162,7 @@ function projection_from_dw_reform_to_master_1()
]
)
col_id = ClMP.VarId(mastervarids[name]; duty = DwSpPrimalSol)
addrow!(pool, col_id, var_ids, vals)
costs_pool[col_id] = 1.0
ClMP.savesolid!(pool_hashtable, col_id, ClMP.PrimalSolution(spform, var_ids, vals, 1.0, ClMP.FEASIBLE_SOL))
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.
Expand Down

0 comments on commit b5ab51d

Please sign in to comment.