Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pool struct for primal solutions of colgen #915

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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