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

[presolve] extract steps of presolve form update #1099

Merged
merged 2 commits into from
Oct 12, 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
186 changes: 108 additions & 78 deletions src/Algorithm/presolve/helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,39 +176,6 @@ function bounds_tightening(form::PresolveFormRepr)
return tightened_bounds
end

function _fix_var(lb::Real, ub::Real, ϵ::Real)
return abs(lb - ub) <= ϵ
end

function vars_to_fix(form::PresolveFormRepr, tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}})
vars_to_fix = Dict{Int, Float64}()
for (col, tb) in tightened_bounds
var_lb, _, var_ub, _ = tb
@assert !isnan(var_lb)
@assert !isnan(var_ub)
if _fix_var(var_lb, var_ub, 1e-6)
vars_to_fix[col] = var_lb
end
end
for col in 1:form.nb_vars
if !haskey(tightened_bounds, col) && _fix_var(form.lbs[col], form.ubs[col], 1e-6)
vars_to_fix[col] = form.lbs[col]
end
end
return vars_to_fix
end

function _check_if_vars_can_be_fixed(vars_to_fix::Dict{Int,Float64}, lbs::Vector{Float64}, ubs::Vector{Float64})
for (col, val) in vars_to_fix
lb = lbs[col]
ub = ubs[col]
if !_fix_var(lb, ub, 1e-6) || !_fix_var(lb, val, 1e-6) || !_fix_var(val, ub, 1e-6)
throw(ArgumentError("Cannot fix variable $col (lb = $lb, ub = $ub, val = $val)."))
end
end
return true
end

function find_uninvolved_vars(col_major_coef_matrix)
uninvolved_vars = Int[]
vals = nonzeros(col_major_coef_matrix)
Expand All @@ -227,21 +194,13 @@ function find_uninvolved_vars(col_major_coef_matrix)
return uninvolved_vars
end

function PresolveFormRepr(
form::PresolveFormRepr,
rows_to_deactivate::Vector{Int},
tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}},
lm,
um;
fix_vars = true
)
nb_cols = form.nb_vars
nb_rows = form.nb_constrs
function tighten_bounds_presolve_form_repr(form::PresolveFormRepr, tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}}, lm, um)
coef_matrix = form.col_major_coef_matrix
rhs = form.rhs
sense = form.sense
lbs = form.lbs
ubs = form.ubs
partial_sol = form.partial_solution

# Tighten bounds
for (col, (lb, tighter_lb, ub, tighter_ub)) in tightened_bounds
Expand All @@ -255,48 +214,119 @@ function PresolveFormRepr(
end
end

# Update partial solution
fixed_col_mask = zeros(Bool, nb_cols)
nb_fixed_vars = 0
row_mask = ones(Bool, form.nb_constrs)
col_mask = ones(Bool, form.nb_vars)

return PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, lm, um),
row_mask,
col_mask
end

function partial_sol_update(form::PresolveFormRepr, lm, um)
coef_matrix = form.col_major_coef_matrix
rhs = form.rhs
sense = form.sense
lbs = form.lbs
ubs = form.ubs

new_partial_sol = zeros(Float64, length(form.partial_solution))
if fix_vars
for (i, (lb, ub)) in enumerate(Iterators.zip(form.lbs, form.ubs))
@assert !isnan(lb)
@assert !isnan(ub)
if lb > ub
error("Infeasible.")
end
if lb > 0.0
@assert !isinf(lb)
new_partial_sol[i] += lb
elseif ub < 0.0 && !isinf(ub)
@assert !isinf(ub)
new_partial_sol[i] += ub
end
if abs(ub - lb) <= Coluna.TOL
fixed_col_mask[i] = true
nb_fixed_vars += 1
end
for (i, (lb, ub)) in enumerate(Iterators.zip(form.lbs, form.ubs))
@assert !isnan(lb)
@assert !isnan(ub)
if lb > ub
error("Infeasible.")
end
if lb > 0.0
@assert !isinf(lb)
new_partial_sol[i] += lb
elseif ub < 0.0 && !isinf(ub)
@assert !isinf(ub)
new_partial_sol[i] += ub
end
end

col_mask = .!fixed_col_mask
nb_cols -= nb_fixed_vars
row_mask = ones(Bool, nb_rows)
row_mask[rows_to_deactivate] .= false

new_sense = sense[row_mask]
new_coef_matrix = coef_matrix[row_mask, col_mask]

# Update rhs
new_rhs = rhs[row_mask] - coef_matrix[row_mask, :] * new_partial_sol
new_rhs = rhs - coef_matrix * new_partial_sol

# Update bounds
new_lbs = lbs[col_mask] - new_partial_sol[col_mask]
new_ubs = ubs[col_mask] - new_partial_sol[col_mask]
new_lbs = lbs - new_partial_sol
new_ubs = ubs - new_partial_sol

# Update partial_sol
partial_sol = form.partial_solution[col_mask] + new_partial_sol[col_mask]
partial_sol = form.partial_solution + new_partial_sol

row_mask = ones(Bool, form.nb_constrs)
col_mask = ones(Bool, form.nb_vars)

return PresolveFormRepr(coef_matrix, new_rhs, sense, new_lbs, new_ubs, partial_sol, lm, um),
row_mask,
col_mask
end

function shrink_presolve_form_repr(form::PresolveFormRepr, rows_to_deactivate::Vector{Int}, lm, um)
nb_cols = form.nb_vars
nb_rows = form.nb_constrs
coef_matrix = form.col_major_coef_matrix
rhs = form.rhs
sense = form.sense
lbs = form.lbs
ubs = form.ubs
partial_sol = form.partial_solution

# Update partial solution
col_mask = ones(Bool, nb_cols)
fixed_vars = Tuple{Int,Float64}[]
for (i, (lb, ub)) in enumerate(Iterators.zip(form.lbs, form.ubs))
@assert !isnan(lb)
@assert !isnan(ub)
if abs(ub) <= Coluna.TOL && abs(lb) <= Coluna.TOL
col_mask[i] = false
push!(fixed_vars, (i, partial_sol[i]))
end
end

nb_cols -= length(fixed_vars)
row_mask = ones(Bool, nb_rows)
row_mask[rows_to_deactivate] .= false

return PresolveFormRepr(
coef_matrix[row_mask, col_mask],
rhs[row_mask],
sense[row_mask],
lbs[col_mask],
ubs[col_mask],
partial_sol[col_mask],
lm,
um
), row_mask, col_mask, fixed_vars
end

function PresolveFormRepr(
presolve_form_repr::PresolveFormRepr,
rows_to_deactivate::Vector{Int},
tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}},
lm,
um;
tighten_bounds = true,
partial_sol = true,
shrink = true,
)
row_mask = ones(Bool, presolve_form_repr.nb_constrs)
col_mask = ones(Bool, presolve_form_repr.nb_vars)
fixed_vars = nothing
if tighten_bounds
presolve_form_repr, row_mask, col_mask = tighten_bounds_presolve_form_repr(
presolve_form_repr, tightened_bounds, lm, um
)
end
if partial_sol
presolve_form_repr, row_mask, col_mask = partial_sol_update(presolve_form_repr, lm, um)
end
if shrink
presolve_form_repr, row_mask, col_mask, fixed_vars = shrink_presolve_form_repr(
presolve_form_repr, rows_to_deactivate, lm, um
)
end
return presolve_form_repr, row_mask, col_mask, fixed_vars
end

return PresolveFormRepr(new_coef_matrix, new_rhs, new_sense, new_lbs, new_ubs, partial_sol, lm, um)
end
122 changes: 64 additions & 58 deletions src/Algorithm/presolve/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,37 +110,46 @@ function propagate_in_presolve_form(
form::PresolveFormulation,
rows_to_deactivate::Vector{Int},
tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}};
fix_vars = true
tighten_bounds = true,
partial_sol = true,
shrink = true,
)
fixed_vars = fix_vars ? vars_to_fix(form.form, tightened_bounds) : Dict{VarId, Float64}()

col_mask = ones(Bool, form.form.nb_vars)
if fix_vars
col_mask[collect(keys(fixed_vars))] .= false
end

row_mask = ones(Bool, form.form.nb_constrs)
row_mask[rows_to_deactivate] .= false
form_repr, row_mask, col_mask, fixed_vars = PresolveFormRepr(
form.form,
rows_to_deactivate,
tightened_bounds,
form.form.lower_multiplicity,
form.form.upper_multiplicity;
tighten_bounds = tighten_bounds,
partial_sol = partial_sol,
shrink = shrink,
)

col_to_var = form.col_to_var[col_mask]
row_to_constr = form.row_to_constr[row_mask]

deactivated_constrs = form.deactivated_constrs
fixed_vars_dict = form.fixed_variables

var_to_col = Dict(getid(var) => k for (k, var) in enumerate(col_to_var))
constr_to_row = Dict(getid(constr) => k for (k, constr) in enumerate(row_to_constr))

deactivated_constrs = form.deactivated_constrs
for constr in form.row_to_constr[rows_to_deactivate]
push!(deactivated_constrs, getid(constr))
end
if shrink
for constr in form.row_to_constr[.!row_mask]
push!(deactivated_constrs, getid(constr))
end

form_repr = PresolveFormRepr(
form.form,
rows_to_deactivate,
tightened_bounds,
form.form.lower_multiplicity,
form.form.upper_multiplicity;
fix_vars = fix_vars
)
if !isnothing(fixed_vars)
for (col, val) in fixed_vars
varid = getid(form.col_to_var[col])
if !haskey(fixed_vars_dict, varid)
fixed_vars_dict[varid] = val
else
error("Cannot fix variable twice.")
end
end
end
end

@assert length(col_to_var) == length(form_repr.lbs)
@assert length(col_to_var) == length(form_repr.ubs)
Expand All @@ -153,7 +162,7 @@ function propagate_in_presolve_form(
constr_to_row,
form_repr,
deactivated_constrs,
fixed_vars
fixed_vars_dict
)
end

Expand All @@ -171,6 +180,7 @@ function create_presolve_reform(reform::Reformulation{DwMaster})
iscuractive(form, constr) && (
getduty(constrid) <= MasterPureConstr ||
getduty(constrid) <= MasterMixedConstr ||
getduty(constrid) <= MasterConvexityConstr ||
getduty(constrid) <= MasterBranchOnOrigVarConstr ||
getduty(constrid) <= MasterUserCutConstr
)
Expand Down Expand Up @@ -336,7 +346,7 @@ function update_reform_from_presolve!(reform::Reformulation{DwMaster}, presolve_
end

println("Updating representative master.")
update_form_from_presolve!(master, presolve_repr_master; update_rhs = false)
update_form_from_presolve!(master, presolve_repr_master)
return
end

Expand Down Expand Up @@ -393,50 +403,46 @@ function run!(algo::PresolveAlgorithm, ::Env, reform::Reformulation, input::Pres

presolve_reform = create_presolve_reform(reform)

# Step 1: we first perform presolve on the restricted master to update the rhs on the constraints
# and determine the new partial solution.
new_restr_master = propagate_in_presolve_form(
presolve_reform.restricted_master,
Int[], # we don't perform constraint deactivation
Dict{Int, Tuple{Float64, Bool, Float64, Bool}}(); # we don't perform bound tightening on the restricted master.
tighten_bounds = false,
shrink = false
)

# Step 2: we propagate the new rhs to the respresentative master.
@assert length(new_restr_master.form.rhs) == length(presolve_reform.original_master.form.rhs)
for (row, rhs) in enumerate(new_restr_master.form.rhs)
presolve_reform.original_master.form.rhs[row] = rhs
end

# Step 3: presolve the respresentative master.
# Bounds tightening, we do not shrink the formulation.
print("Presolving representative master #1. ")
tightened_bounds_repr = bounds_tightening(presolve_reform.original_master.form)
print("$(length(tightened_bounds_repr)) tightened bounds. ")
repr_deactivate_constr = rows_to_deactivate(presolve_reform.original_master.form)
new_original_master = propagate_in_presolve_form(presolve_reform.original_master, repr_deactivate_constr, tightened_bounds_repr; fix_vars = false)

print("Presolving restricted master #1. ")
tightened_bounds_restr = bounds_tightening(presolve_reform.restricted_master.form)
println("$(length(tightened_bounds_restr)) tightened bounds. ")
restr_deactivate_constr = rows_to_deactivate(presolve_reform.restricted_master.form)
new_restricted_master = propagate_in_presolve_form(presolve_reform.restricted_master, restr_deactivate_constr, tightened_bounds_restr)
new_repr_master = propagate_in_presolve_form(
presolve_reform.original_master, Int[], tightened_bounds_repr; shrink = false
)

presolve_reform.restricted_master = new_restricted_master
presolve_reform.original_master = new_original_master
presolve_reform.restricted_master = new_restr_master
presolve_reform.original_master = new_repr_master

propagate_local_and_global_bounds!(reform, presolve_reform) # TODO: cannot perform this operation twice.

print("Presolving representative master #2. ")
tightened_bounds_repr = bounds_tightening(presolve_reform.original_master.form)
println("$(length(tightened_bounds_repr)) tightened bounds.")
new_original_master = propagate_in_presolve_form(presolve_reform.original_master, Int[], tightened_bounds_repr; fix_vars = false)

print("Presolving restricted master #2. ")
tightened_bounds_restr = bounds_tightening(presolve_reform.restricted_master.form)
println("$(length(tightened_bounds_restr)) tightened bounds.")
new_restricted_master = propagate_in_presolve_form(presolve_reform.restricted_master, Int[], tightened_bounds_restr)

presolve_reform.restricted_master = new_restricted_master
presolve_reform.original_master = new_original_master

#propagate_local_and_global_bounds!(reform, presolve_reform)

# # Compute global bounds of aggregated variables.
# new_original_master = compute_global_bounds(presolve_reform.original_master, new_restricted_master)

# # Propagate bounds from the original master to the subproblems
# for (spid, sp) in presolve_reform.dw_sps
# propagate_var_bounds_from!(new_original_master, sp)
# end
new_restr_master = propagate_in_presolve_form(
presolve_reform.restricted_master,
Int[],
Dict{Int, Tuple{Float64, Bool, Float64, Bool}}();
tighten_bounds = false,
)
presolve_reform.restricted_master = new_restr_master

update_reform_from_presolve!(reform, presolve_reform)

@show getmaster(reform)

return PresolveOutput(true)
end

Expand Down
Loading
Loading