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] first version; broken unit tests #1100

Merged
merged 1 commit 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
39 changes: 36 additions & 3 deletions src/Algorithm/presolve/helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,45 @@ mutable struct PresolveFormRepr
lbs::Vector{Float64} # on variables
ubs::Vector{Float64} # on variables
partial_solution::Vector{Float64} # on variables
unpropagated_partial_solution::Vector{Float64} # on variables, to update local bounds
unpropagated_partial_solution_flag::Bool
lower_multiplicity::Float64
upper_multiplicity::Float64
end

function PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, lm, um)
function PresolveFormRepr(
coef_matrix, rhs, sense, lbs, ubs, partial_solution, lm, um;
unpropagated_partial_solution = nothing
)
length(lbs) == length(ubs) || throw(ArgumentError("Inconsistent sizes of bounds and coef_matrix."))
length(rhs) == length(sense) || throw(ArgumentError("Inconsistent sizes of rhs and coef_matrix."))
nb_vars = length(lbs)
nb_constrs = length(rhs)
@assert reduce(&, map(lb -> !isnan(lb), lbs))
@assert reduce(&, map(ub -> !isnan(ub), ubs))

if isnothing(unpropagated_partial_solution)
unpropagated_partial_solution_vec = zeros(Float64, nb_vars)
unpropagated_partial_solution_flag = false
else
unpropagated_partial_solution_vec = unpropagated_partial_solution
unpropagated_partial_solution_flag = true
end

return PresolveFormRepr(
nb_vars, nb_constrs, coef_matrix, transpose(coef_matrix), rhs, sense, lbs, ubs, partial_solution, lm, um
nb_vars,
nb_constrs,
coef_matrix,
transpose(coef_matrix),
rhs,
sense,
lbs,
ubs,
partial_solution,
unpropagated_partial_solution_vec,
unpropagated_partial_solution_flag,
lm,
um
)
end

Expand Down Expand Up @@ -258,7 +284,10 @@ function partial_sol_update(form::PresolveFormRepr, lm, um)
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),
return PresolveFormRepr(
coef_matrix, new_rhs, sense, new_lbs, new_ubs, partial_sol, lm, um;
unpropagated_partial_solution = new_partial_sol
),
row_mask,
col_mask
end
Expand All @@ -273,6 +302,10 @@ function shrink_presolve_form_repr(form::PresolveFormRepr, rows_to_deactivate::V
ubs = form.ubs
partial_sol = form.partial_solution

if form.unpropagated_partial_solution_flag
error("Cannot shrink a formulation that contains an unpropagated partial solution.")
end

# Update partial solution
col_mask = ones(Bool, nb_cols)
fixed_vars = Tuple{Int,Float64}[]
Expand Down
75 changes: 43 additions & 32 deletions src/Algorithm/presolve/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -403,43 +403,54 @@ 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
)
for i in 1:3
println("**** Presolve step $i ****")
# 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 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. ")
new_repr_master = propagate_in_presolve_form(
presolve_reform.original_master, Int[], tightened_bounds_repr; shrink = false
)
# 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)
println("$(length(tightened_bounds_repr)) tightened bounds. ")
new_repr_master = propagate_in_presolve_form(
presolve_reform.original_master, Int[], tightened_bounds_repr; shrink = false
)

presolve_reform.restricted_master = new_restr_master
presolve_reform.original_master = new_repr_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.
# Step 4: Propagate the partial solution to the local bounds.
propagate_partial_sol_to_local_bounds!(reform, presolve_reform)

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
# Step 5: Propagate and strengthen local and global bounds.
propagate_local_to_global_bounds!(reform, presolve_reform)
propagate_global_to_local_bounds!(reform, presolve_reform)
propagate_local_to_global_bounds!(reform, presolve_reform)

# Step 6: Shrink the formulation (remove fixed variables).
new_restr_master = propagate_in_presolve_form(
presolve_reform.restricted_master,
Int[],
Dict{Int, Tuple{Float64, Bool, Float64, Bool}}();
tighten_bounds = false,
partial_sol = false
)
presolve_reform.restricted_master = new_restr_master
end

update_reform_from_presolve!(reform, presolve_reform)

Expand Down
69 changes: 59 additions & 10 deletions src/Algorithm/presolve/propagation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,24 @@ function propagate_var_bounds_from!(dest::PresolveFormulation, src::PresolveForm
return
end

function propagate_local_and_global_bounds!(reform::Reformulation, presolve_form_repr::DwPresolveReform)
function propagate_partial_sol_to_local_bounds!(reform::Reformulation, presolve_form_repr::DwPresolveReform)
master = getmaster(reform)
presolve_restr_master = presolve_form_repr.restricted_master
presolve_repr_master = presolve_form_repr.original_master
for (spid, spform) in get_dw_pricing_sps(reform)
presolve_sp = presolve_form_repr.dw_sps[spid]
propagate_local_bounds!(presolve_sp, presolve_restr_master, spform, master)
propagate_partial_sol_to_local_bounds!(presolve_sp, presolve_restr_master, spform, master)
end
presolve_restr_master.form.unpropagated_partial_solution = zeros(Float64, presolve_restr_master.form.nb_vars)
presolve_restr_master.form.unpropagated_partial_solution_flag = false
return
end

function propagate_local_to_global_bounds!(reform::Reformulation, presolve_reform_repr::DwPresolveReform)
master = getmaster(reform)
presolve_repr_master = presolve_reform_repr.original_master
for (spid, spform) in get_dw_pricing_sps(reform)
presolve_sp = presolve_reform_repr.dw_sps[spid]
propagate_global_bounds!(presolve_repr_master, master, presolve_sp, spform)
propagate_local_bounds!(presolve_sp, presolve_repr_master, spform, master)
end
return
end
Expand All @@ -76,16 +85,56 @@ function propagate_global_bounds!(presolve_repr_master::PresolveFormulation, mas
for (i, var) in enumerate(presolve_sp.col_to_var)
repr_col = get(presolve_repr_master.var_to_col, getid(var), nothing)
if !isnothing(repr_col)
lb = presolve_sp.form.lbs[i]
ub = presolve_sp.form.ubs[i]
presolve_repr_master.form.lbs[repr_col] = lb * (lb < 0 ? um : lm)
presolve_repr_master.form.ubs[repr_col] = ub * (ub < 0 ? lm : um)
local_lb = presolve_sp.form.lbs[i]
local_ub = presolve_sp.form.ubs[i]
global_lb = presolve_repr_master.form.lbs[repr_col]
global_ub = presolve_repr_master.form.ubs[repr_col]
new_global_lb = local_lb * (local_lb < 0 ? um : lm)
new_global_ub = local_ub * (local_ub < 0 ? lm : um)
presolve_repr_master.form.lbs[repr_col] = max(global_lb, new_global_lb)
presolve_repr_master.form.ubs[repr_col] = min(global_ub, new_global_ub)
end
end
return
end

function propagate_global_to_local_bounds!(reform::Reformulation, presolve_reform_repr::DwPresolveReform)
master = getmaster(reform)
presolve_repr_master = presolve_reform_repr.original_master
for (spid, spform) in get_dw_pricing_sps(reform)
presolve_sp = presolve_reform_repr.dw_sps[spid]
propagate_local_bounds!(presolve_repr_master, master, presolve_sp, spform)
end
return
end

function propagate_local_bounds!(presolve_repr_master::PresolveFormulation, master::Formulation, presolve_sp::PresolveFormulation, spform::Formulation)
# TODO: does not work with representatives of multiple subproblems.
lm = presolve_sp.form.lower_multiplicity
um = presolve_sp.form.upper_multiplicity
for (i, var) in enumerate(presolve_sp.col_to_var)
repr_col = get(presolve_repr_master.var_to_col, getid(var), nothing)
if !isnothing(repr_col)
global_lb = presolve_repr_master.form.lbs[repr_col]
global_ub = presolve_repr_master.form.ubs[repr_col]
local_lb = presolve_sp.form.lbs[i]
local_ub = presolve_sp.form.ubs[i]

if !isinf(global_lb) && !isinf(local_ub)
new_local_lb = global_lb - local_ub * (local_ub < 0 ? um : lm)
presolve_sp.form.lbs[i] = max(new_local_lb, local_lb)
end

if !isinf(global_ub) && !isinf(local_lb)
new_local_ub = global_ub - local_lb * (local_lb < 0 ? lm : um)
presolve_sp.form.ubs[i] = min(new_local_ub, local_ub)
end
end
end
return
end

function propagate_local_bounds!(presolve_sp::PresolveFormulation, presolve_master::PresolveFormulation, spform::Formulation, master::Formulation)
function propagate_partial_sol_to_local_bounds!(presolve_sp::PresolveFormulation, presolve_master::PresolveFormulation, spform::Formulation, master::Formulation)
partial_solution = zeros(Float64, presolve_sp.form.nb_vars)

# Get new columns in partial solution.
Expand All @@ -94,7 +143,7 @@ function propagate_local_bounds!(presolve_sp::PresolveFormulation, presolve_mast
# Get new columns in partial solution.
nb_fixed_columns = 0
new_column_explored = true
for (col, partial_sol_value) in enumerate(presolve_master.form.partial_solution)
for (col, partial_sol_value) in enumerate(presolve_master.form.unpropagated_partial_solution)
if abs(partial_sol_value) > Coluna.TOL
var = presolve_master.col_to_var[col]
varid = getid(var)
Expand Down
8 changes: 4 additions & 4 deletions test/unit/Presolve/helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function test_presolve_builder2()
rows_to_deactivate = [1, 3, 6]
tightened_bounds = Dict{Int, Tuple{Float64, Bool, Float64, Bool}}()

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
@test form2.nb_vars == 7
@test form2.nb_constrs == 3
@test all(form2.col_major_coef_matrix .== coef_matrix[[2, 4, 5], :])
Expand Down Expand Up @@ -139,7 +139,7 @@ function test_presolve_builder3()
# <= -1 - 2 + 4 -> 1
# == -6 +2*2 - 5.5 -> -7.5

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
@test form2.nb_vars == 3
@test form2.nb_constrs == 6
@test all(form2.col_major_coef_matrix .== coef_matrix[:, [2, 4, 5]])
Expand Down Expand Up @@ -177,7 +177,7 @@ function test_presolve_builder4()
3 => (-1, false, 3, false),
6 => (0.5, true, 0.5, true) # the flag forces the update!
)
form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
@test form2.nb_vars == 6
@test form2.nb_constrs == 6
@test all(form2.col_major_coef_matrix .== coef_matrix[:, [1, 2, 3, 4, 5, 7]])
Expand Down Expand Up @@ -211,7 +211,7 @@ function test_presolve_builder5()
3 => (1, true, 2, true)
)

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1.0, 1.0)
@test form2.nb_vars == 3
@test form2.nb_constrs == 2
@test all(form2.col_major_coef_matrix .== coef_matrix)
Expand Down
8 changes: 4 additions & 4 deletions test/unit/Presolve/partial_solution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function test_partial_solution1()
2 => (2, true, Inf, false)
)

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
@test form2.nb_vars == 2
@test form2.nb_constrs == 1
@test all(form2.col_major_coef_matrix .== coef_matrix)
Expand Down Expand Up @@ -68,7 +68,7 @@ function test_partial_solution2()
2 => (2, true, Inf, false)
)

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
@test form2.nb_vars == 2
@test form2.nb_constrs == 1
@test all(form2.col_major_coef_matrix .== coef_matrix)
Expand Down Expand Up @@ -109,7 +109,7 @@ function test_partial_solution3()
2 => (-10, false, 8, true)
)

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
@test form2.nb_vars == 2
@test form2.nb_constrs == 2
@test all(form2.col_major_coef_matrix .== coef_matrix)
Expand Down Expand Up @@ -149,7 +149,7 @@ function test_partial_solution4()
2 => (-Inf, false, -1.0, true)
)

form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, tightened_bounds, 1, 1)
@test form2.nb_vars == 2
@test form2.nb_constrs == 2
@test all(form2.col_major_coef_matrix .== coef_matrix)
Expand Down
14 changes: 9 additions & 5 deletions test/unit/Presolve/propagation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,10 @@ function test_var_bound_propagation_within_restricted_master()
)

result = Coluna.Algorithm.bounds_tightening(master_presolve_form.form)
new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form(master_presolve_form, Int[], result)
new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form(master_presolve_form, Int[], result; shrink = false)
new_master_presolve_form.form.unpropagated_partial_solution_flag = false
no_tightening = Dict{Int, Tuple{Float64, Bool, Float64, Bool}}()
new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form(new_master_presolve_form, Int[], no_tightening; tighten_bounds=false, partial_sol = false)

Coluna.Algorithm.update_form_from_presolve!(master_form, new_master_presolve_form)

Expand Down Expand Up @@ -432,12 +435,13 @@ function test_col_bounds_propagation_from_restricted_master()

## We run the presolve on the restricted master
tightened_bounds = Coluna.Algorithm.bounds_tightening(restricted_presolve_form.form)
new_restricted_master = Coluna.Algorithm.propagate_in_presolve_form(
restricted_presolve_form, Int[], tightened_bounds
)
new_restricted_master = Coluna.Algorithm.propagate_in_presolve_form(restricted_presolve_form, Int[], tightened_bounds; shrink = false)
new_restricted_master.form.unpropagated_partial_solution_flag = false
no_tightening = Dict{Int, Tuple{Float64, Bool, Float64, Bool}}()
new_restricted_master = Coluna.Algorithm.propagate_in_presolve_form(new_restricted_master, Int[], no_tightening; tighten_bounds=false, partial_sol = false)

Coluna.Algorithm.update_form_from_presolve!(master_form, new_restricted_master)
Coluna.Algorithm.propagate_local_bounds!(sp_presolve_form, new_restricted_master, spform, master_form)
Coluna.Algorithm.propagate_local_bounds!(sp_presolve_form, spform, new_restricted_master, master_form)
Coluna.Algorithm.propagate_global_bounds!(master_presolve_form, master_form, sp_presolve_form, spform)
Coluna.Algorithm.update_form_from_presolve!(spform, sp_presolve_form)
Coluna.Algorithm.update_form_from_presolve!(master_form, master_presolve_form)
Expand Down
Loading