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

Update of formulation storages #1103

Merged
merged 2 commits into from
Oct 25, 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
1 change: 1 addition & 0 deletions src/Algorithm/basic/solveipform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function get_units_usage(
push!(units_usage, (form, MasterColumnsUnit, READ_ONLY))
push!(units_usage, (form, MasterBranchConstrsUnit, READ_ONLY))
push!(units_usage, (form, MasterCutsUnit, READ_ONLY))
push!(units_usage, (form, PartialSolutionUnit, READ_ONLY))
end
return units_usage
end
Expand Down
1 change: 1 addition & 0 deletions src/Algorithm/basic/solvelpform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function get_units_usage(
push!(units_usage, (form, StaticVarConstrUnit, READ_ONLY))
if Duty <: MathProg.AbstractMasterDuty
push!(units_usage, (form, MasterColumnsUnit, READ_ONLY))
push!(units_usage, (form, PartialSolutionUnit, READ_ONLY))
push!(units_usage, (form, MasterBranchConstrsUnit, READ_ONLY))
push!(units_usage, (form, MasterCutsUnit, READ_ONLY))
end
Expand Down
1 change: 1 addition & 0 deletions src/Algorithm/benders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function get_units_usage(algo::BendersCutGeneration, reform::Reformulation)

# TO DO : everything else should be communicated by the child algorithms
push!(units_usage, (master, StaticVarConstrUnit, READ_ONLY))
push!(units_usage, (master, PartialSolutionUnit, READ_ONLY))
push!(units_usage, (master, MasterBranchConstrsUnit, READ_ONLY))
push!(units_usage, (master, MasterColumnsUnit, READ_ONLY))
for (_, spform) in get_benders_sep_sps(reform)
Expand Down
1 change: 1 addition & 0 deletions src/Algorithm/colgen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ function get_units_usage(algo::ColumnGeneration, reform::Reformulation)
master = getmaster(reform)
push!(units_usage, (master, MasterColumnsUnit, READ_AND_WRITE))
push!(units_usage, (master, StaticVarConstrUnit, READ_ONLY))
push!(units_usage, (master, PartialSolutionUnit, READ_ONLY))

# as column generation may call essential cut callbacks
# TO DO: it would be good to verify first whether any callback is really defined
Expand Down
102 changes: 71 additions & 31 deletions src/Algorithm/formstorages.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
"""
VarState
StaticVarState

Used in formulation records
"""
struct VarState
struct StaticVarState
cost::Float64
lb::Float64
ub::Float64
partial_sol_value::Float64
end

function apply_state!(form::Formulation, var::Variable, var_state::VarState)
# To avoid warnings when changing variable bounds.
# Commented, as this line makes var.curdata.is_in_partial_sol and form.manager.partial_solution asynchronous (source of diving with LDS bug)
# var.curdata.is_in_partial_sol = false

function apply_state!(form::Formulation, var::Variable, var_state::StaticVarState)
if getcurlb(form, var) != var_state.lb
setcurlb!(form, var, var_state.lb)
end
Expand All @@ -24,9 +19,6 @@ function apply_state!(form::Formulation, var::Variable, var_state::VarState)
if getcurcost(form, var) != var_state.cost
setcurcost!(form, var, var_state.cost)
end
if MathProg.get_value_in_partial_sol(form, var) != var_state.partial_sol_value
MathProg.set_value_in_partial_solution!(form, var, var_state.partial_sol_value)
end
return
end

Expand Down Expand Up @@ -118,8 +110,11 @@ Can be restored using a MasterColumnsRecord.
struct MasterColumnsUnit <: AbstractRecordUnit end

mutable struct MasterColumnsRecord <: AbstractRecord
cols::Dict{VarId,VarState}
active_cols::Set{VarId}

MasterColumnsRecord() = new(Set{VarId}())
end
push!(record::MasterColumnsRecord, id::VarId) = push!(record.active_cols, id)

struct MasterColumnsKey <: AbstractStorageUnitKey end

Expand All @@ -129,16 +124,10 @@ record_type_from_key(::MasterColumnsKey) = MasterColumnsRecord
ClB.storage_unit(::Type{MasterColumnsUnit}, _) = MasterColumnsUnit()

function ClB.record(::Type{MasterColumnsRecord}, id::Int, form::Formulation, unit::MasterColumnsUnit)
record = MasterColumnsRecord(Dict{VarId,ConstrState}())
record = MasterColumnsRecord()
for (id, var) in getvars(form)
if getduty(id) <= MasterCol && isexplicit(form, var) && iscuractive(form, var)
varstate = VarState(
getcurcost(form, var),
getcurlb(form, var),
getcurub(form, var),
MathProg.get_value_in_partial_sol(form, var)
)
record.cols[id] = varstate
push!(record, id)
end
end
return record
Expand All @@ -152,16 +141,12 @@ function ClB.restore_from_record!(
)
for (id, var) in getvars(form)
if getduty(id) <= MasterCol && isexplicit(form, var)
if haskey(state.cols, id)
if id in state.active_cols
if !iscuractive(form, var)
activate!(form, var)
end
apply_state!(form, var, state.cols[id])
else
if iscuractive(form, var)
if !iszero(MathProg.get_value_in_partial_sol(form, var))
MathProg.set_value_in_partial_solution!(form, var, 0.0)
end
deactivate!(form, var)
end
end
Expand Down Expand Up @@ -245,7 +230,7 @@ StaticVarConstrUnit(::Formulation) = StaticVarConstrUnit()

mutable struct StaticVarConstrRecord <: AbstractRecord
constrs::Dict{ConstrId,ConstrState}
vars::Dict{VarId,VarState}
vars::Dict{VarId,StaticVarState}
end

# TO DO: we need to keep here only the difference with the initial data
Expand All @@ -271,7 +256,7 @@ ClB.storage_unit(::Type{StaticVarConstrUnit}, _) = StaticVarConstrUnit()

function ClB.record(::Type{StaticVarConstrRecord}, id::Int, form::Formulation, unit::StaticVarConstrUnit)
@logmsg LogLevel(-2) string("Storing static vars and consts")
record = StaticVarConstrRecord(Dict{ConstrId,ConstrState}(), Dict{VarId,VarState}())
record = StaticVarConstrRecord(Dict{ConstrId,ConstrState}(), Dict{VarId,StaticVarState}())
for (id, constr) in getconstrs(form)
if isaStaticDuty(getduty(id)) && iscuractive(form, constr) && isexplicit(form, constr)
constrstate = ConstrState(getcurrhs(form, constr))
Expand All @@ -280,11 +265,10 @@ function ClB.record(::Type{StaticVarConstrRecord}, id::Int, form::Formulation, u
end
for (id, var) in getvars(form)
if isaStaticDuty(getduty(id)) && isexplicit(form, var) && iscuractive(form, var)
varstate = VarState(
varstate = StaticVarState(
getcurcost(form, var),
getcurlb(form, var),
getcurub(form, var),
MathProg.get_value_in_partial_sol(form, var)
getcurub(form, var)
)
record.vars[id] = varstate
end
Expand Down Expand Up @@ -321,7 +305,7 @@ function ClB.restore_from_record!(
if isaStaticDuty(getduty(id)) && isexplicit(form, var)
@logmsg LogLevel(-4) "Checking " getname(form, var)
if haskey(record.vars, id)
if !iscuractive(form, var) #&& !isfixed(form, var)
if !iscuractive(form, var)
@logmsg LogLevel(-4) string("Activating variable", getname(form, var))
activate!(form, var)
end
Expand All @@ -336,3 +320,59 @@ function ClB.restore_from_record!(
end
end
end

"""
PartialSolutionUnit

Unit for current the partial solution of a formulation.
Can be restored using a PartialSolutionRecord.
"""

struct PartialSolutionUnit <: AbstractRecordUnit end

PartialSolutionUnit(::Formulation) = PartialSolutionUnit()

mutable struct PartialSolutionRecord <: AbstractRecord
partial_solution::Dict{VarId, Float64}

PartialSolutionRecord(form::Formulation) = new(copy(MathProg.getpartialsol(form)))
end

struct PartialSolutionKey <: AbstractStorageUnitKey end

key_from_storage_unit_type(::Type{PartialSolutionUnit}) = PartialSolutionKey()
record_type_from_key(::PartialSolutionKey) = PartialSolutionRecord

ClB.storage_unit(::Type{PartialSolutionUnit}, _) = PartialSolutionUnit()

function ClB.record(::Type{PartialSolutionRecord}, id::Int, form::Formulation, unit::PartialSolutionUnit)
return PartialSolutionRecord(form)
end

ClB.record_type(::Type{PartialSolutionUnit}) = PartialSolutionRecord
ClB.storage_unit_type(::Type{PartialSolutionRecord}) = PartialSolutionUnit

function ClB.restore_from_record!(
form::Formulation, ::PartialSolutionUnit, record::PartialSolutionRecord
)
@logmsg LogLevel(-2) "Restoring partial solution"
form_part_sol = MathProg.getpartialsol(form)
change_dict = Dict{VarId, Float64}()
for (var_id, cur_value) in form_part_sol
record_value = get(record.partial_solution, var_id, 0.0)
if cur_value != record_value
change_dict[var_id] = record_value
end
end

for (var_id, record_value) in record.partial_solution
if !haskey(form_part_sol, var_id)
change_dict[var_id] = record_value
end
end

for (var_id, value) in change_dict
@logmsg LogLevel(-4) string("Changing value of ", getname(form, var_id), " in partial solution to ", value)
MathProg.set_value_in_partial_solution!(form, var_id, value)
end
end
12 changes: 8 additions & 4 deletions src/Algorithm/presolve/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ function update_partial_sol!(form::Formulation{DwMaster}, presolve_form::Presolv
""")
end
if !iszero(val) && (duty <= MasterCol || duty <= MasterPureVar)
println("update partial sol for variable $(getname(form, var)).")
MathProg.add_to_partial_solution!(form, var, val)
partial_sol_counter += 1
setcurlb!(form, var, 0.0)
Expand Down Expand Up @@ -370,6 +369,7 @@ function get_units_usage(algo::PresolveAlgorithm, reform::Reformulation)
units_usage = Tuple{AbstractModel, UnitType, UnitPermission}[]
master = getmaster(reform)
push!(units_usage, (master, StaticVarConstrUnit, READ_AND_WRITE))
push!(units_usage, (master, PartialSolutionUnit, READ_AND_WRITE))
push!(units_usage, (master, MasterBranchConstrsUnit, READ_AND_WRITE))
push!(units_usage, (master, MasterCutsUnit, READ_AND_WRITE))
push!(units_usage, (master, MasterColumnsUnit, READ_AND_WRITE))
Expand Down Expand Up @@ -550,9 +550,7 @@ function deactivate_non_proper_columns!(master::Formulation{DwMaster}, dw_sps)
if getduty(varid) <= MasterCol
spid = getoriginformuid(varid)
if !_column_is_proper(varid, dw_sps[spid])
if !in_partial_solution(master, varid)
deactivate!(master, varid)
end
deactivate!(master, varid)
end
end
end
Expand Down Expand Up @@ -627,3 +625,9 @@ function _column_is_proper(col_id, sp_form)
end
return true
end

function column_is_proper(col_id, reform)
sp_id = getoriginformuid(col_id)
sp_form = get_dw_pricing_sps(reform)[sp_id]
return _column_is_proper(col_id, sp_form)
end
20 changes: 10 additions & 10 deletions src/MathProg/varconstr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ subsolver.
If the variable had fixed value, it unfixes the variable.
"""
function setcurlb!(form::Formulation, var::Variable, lb)
if in_partial_sol(form, var) && !(getduty(getid(var)) <= MasterCol)
@warn "Changing lower bound of fixed variable."
end
# if in_partial_sol(form, var) && !(getduty(getid(var)) <= MasterCol)
# @warn "Changing lower bound of fixed variable."
# end

var.curdata.lb = lb
if isexplicit(form, var) && iscuractive(form, var)
Expand Down Expand Up @@ -194,9 +194,9 @@ subsolver.
If the variable had fixed value, it unfixes the variable.
"""
function setcurub!(form::Formulation, var::Variable, ub)
if in_partial_sol(form, var)
@warn "Changing upper bound of fixed variable."
end
# if in_partial_sol(form, var)
# @warn "Changing upper bound of fixed variable."
# end

var.curdata.ub = ub
if isexplicit(form, var) && iscuractive(form, var)
Expand Down Expand Up @@ -244,15 +244,15 @@ function add_to_partial_solution!(form::Formulation, varid::VarId, value, propag
end

function add_to_partial_solution!(form::Formulation, var::Variable, value, propagation = false)
if isexplicit(form, var) && iscuractive(form, var)
if isexplicit(form, var)
cumulative_val = _add_partial_value!(form.manager, var, value)
if propagation
_propagate_partial_value_bounds!(form, var, cumulative_val)
end
return true
end
name = getname(form, var)
@warn "Cannot add variable $name to partial solution because it is unactive or non-explicit."
@warn "Cannot add variable $name to partial solution because it is non-explicit."
return false
end

Expand All @@ -263,12 +263,12 @@ function set_value_in_partial_solution!(form::Formulation, varid::VarId, value)
end

function set_value_in_partial_solution!(form::Formulation, var::Variable, value)
if isexplicit(form, var) && iscuractive(form, var)
if isexplicit(form, var)
_set_partial_value!(form.manager, var, value)
return true
end
name = getname(form, var)
@warn "Cannot set variable $name to partial solution because it is unactive or non-explicit."
@warn "Cannot set variable $name to partial solution because it is non-explicit."
return false
end

Expand Down
40 changes: 9 additions & 31 deletions test/unit/Algorithm/record_mastercolumns.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
function unit_master_columns_record()
function test_record(state, cost, lb, ub)
@test state.cost == cost
@test state.lb == lb
@test state.ub == ub
end

function test_var(form, var, cost, lb, ub)
@test ClMP.getcurcost(form, var) == cost
@test ClMP.getcurlb(form, var) == lb
@test ClMP.getcurub(form, var) == ub
end

env = CL.Env{ClMP.VarId}(CL.Params())

Expand Down Expand Up @@ -44,33 +33,22 @@ function unit_master_columns_record()
storage = ClB.getstorage(form)
r1 = ClB.create_record(storage, ClA.MasterColumnsUnit)

@test isempty(setdiff(keys(r1.cols), ClMP.getid.(values(vars))))
test_record(r1.cols[ClMP.getid(vars["v1"])], 1, 0, Inf)
test_record(r1.cols[ClMP.getid(vars["v2"])], 2, 0, Inf)
test_record(r1.cols[ClMP.getid(vars["v3"])], 4, 0, Inf)
@test isempty(setdiff(r1.active_cols, ClMP.getid.(values(vars))))

# make changes on the formulation
ClMP.setcurlb!(form, vars["v1"], 5.0)
ClMP.setcurub!(form, vars["v2"], 12.0)
ClMP.setcurcost!(form, vars["v3"], 4.6)
ClMP.deactivate!(form, vars["v2"])

r2 = ClB.create_record(storage, ClA.MasterColumnsUnit)

@test isempty(setdiff(keys(r2.cols), ClMP.getid.(values(vars))))
test_record(r2.cols[ClMP.getid(vars["v1"])], 1, 5, Inf)
test_record(r2.cols[ClMP.getid(vars["v2"])], 2, 0, 12)
test_record(r2.cols[ClMP.getid(vars["v3"])], 4.6, 0, Inf)
v1v3 = Set{ClMP.VarId}([ClMP.getid(vars["v1"]), ClMP.getid(vars["v3"])])
@test isempty(setdiff(r2.active_cols, v1v3))

ClB.restore_from_record!(storage, r1)

test_var(form, vars["v1"], 1, 0, Inf)
test_var(form, vars["v2"], 2, 0, Inf)
test_var(form, vars["v3"], 4, 0, Inf)
active_varids = filter(var_id -> iscuractive(form, var_id), keys(ClMP.getvars(form)))
@test isempty(setdiff(active_varids, ClMP.getid.(values(vars))))

ClB.restore_from_record!(storage, r2)

test_var(form, vars["v1"], 1, 5, Inf)
test_var(form, vars["v2"], 2, 0, 12)
test_var(form, vars["v3"], 4.6, 0, Inf)
active_varids = filter(var_id -> iscuractive(form, var_id), keys(ClMP.getvars(form)))
@test isempty(setdiff(active_varids, v1v3))
end
register!(unit_tests, "master_columns_record", unit_master_columns_record)
register!(unit_tests, "storage_record", unit_master_columns_record, f = true)
Loading
Loading