From bd68509da1b9501572e26da3ee98c772e7c48492 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Tue, 19 Sep 2023 15:45:30 +0200 Subject: [PATCH] Presolve new var fixing using partial solution (#1065) * change how variable fixing is handled * ok --- .../branching/single_var_branching.jl | 2 +- src/Algorithm/presolve/helpers.jl | 68 ++++++---- src/Algorithm/presolve/interface.jl | 38 +++--- src/Algorithm/presolve/propagation.jl | 8 +- test/unit/Presolve/helpers.jl | 119 +++++++++++++----- test/unit/Presolve/propagation.jl | 104 ++++++--------- 6 files changed, 196 insertions(+), 143 deletions(-) diff --git a/src/Algorithm/branching/single_var_branching.jl b/src/Algorithm/branching/single_var_branching.jl index 369298e2f..e56e3bbc9 100644 --- a/src/Algorithm/branching/single_var_branching.jl +++ b/src/Algorithm/branching/single_var_branching.jl @@ -142,4 +142,4 @@ function Branching.apply_branching_rule(::SingleVarBranchingRule, env::Env, refo return collection end return candidates -end \ No newline at end of file +end \ No newline at end of file diff --git a/src/Algorithm/presolve/helpers.jl b/src/Algorithm/presolve/helpers.jl index 95ec84062..3c55f58b1 100644 --- a/src/Algorithm/presolve/helpers.jl +++ b/src/Algorithm/presolve/helpers.jl @@ -12,17 +12,18 @@ struct PresolveFormRepr sense::Vector{ConstrSense} # on constraints lbs::Vector{Float64} # on variables ubs::Vector{Float64} # on variables + partial_solution::Vector{Float64} # on variables lower_multiplicity::Float64 upper_multiplicity::Float64 end -function PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, lm, um) +function PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, lm, um) 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) return PresolveFormRepr( - nb_vars, nb_constrs, coef_matrix, transpose(coef_matrix), rhs, sense, lbs, ubs, lm, um + nb_vars, nb_constrs, coef_matrix, transpose(coef_matrix), rhs, sense, lbs, ubs, partial_solution, lm, um ) end @@ -192,10 +193,9 @@ end function PresolveFormRepr( form::PresolveFormRepr, rows_to_deactivate::Vector{Int}, - vars_to_fix::Dict{Int, Float64}, tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}}, - lm::Float64, - um::Float64 + lm, + um ) nb_cols = form.nb_vars nb_rows = form.nb_constrs @@ -205,19 +205,7 @@ function PresolveFormRepr( lbs = form.lbs ubs = form.ubs - col_mask = ones(Bool, nb_cols) - col_mask[collect(keys(vars_to_fix))] .= false - fixed_col_mask = .!col_mask - row_mask = ones(Bool, nb_rows) - row_mask[rows_to_deactivate] .= false - - # Deactivate rows - new_coef_matrix = coef_matrix[row_mask, col_mask] - - new_rhs = rhs[row_mask] - new_sense = sense[row_mask] - - # Tighten Bounds + # Tighten bounds for (col, (lb, tighter_lb, ub, tighter_ub)) in tightened_bounds if tighter_lb lbs[col] = lb @@ -227,14 +215,42 @@ function PresolveFormRepr( end end - # Fix variables - # Make sure we can fix the variable. - _check_if_vars_can_be_fixed(vars_to_fix, lbs, ubs) - + # Update partial solution + fixed_col_mask = zeros(Bool, nb_cols) + nb_fixed_vars = 0 + new_partial_sol = zeros(Float64, length(form.partial_solution)) + for (i, (lb, ub)) in enumerate(Iterators.zip(form.lbs, form.ubs)) + if lb > ub + error("Infeasible.") + end + if lb > 0.0 + new_partial_sol[i] += lb + elseif ub < 0.0 + new_partial_sol[i] += ub + end + if abs(ub - lb) <= Coluna.TOL + fixed_col_mask[i] = true + nb_fixed_vars += 1 + 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 = new_rhs - coef_matrix[row_mask, fixed_col_mask] * lbs[fixed_col_mask] - new_lbs = lbs[col_mask] - new_ubs = ubs[col_mask] + new_rhs = rhs[row_mask] - coef_matrix[row_mask, :] * 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] + + # Update partial_sol + partial_sol = form.partial_solution[col_mask] + new_partial_sol[col_mask] - return PresolveFormRepr(new_coef_matrix, new_rhs, new_sense, new_lbs, new_ubs, lm, um) + return PresolveFormRepr(new_coef_matrix, new_rhs, new_sense, new_lbs, new_ubs, partial_sol, lm, um) end \ No newline at end of file diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index 702fbac71..7bfdf1ece 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -5,7 +5,7 @@ struct PresolveFormulation constr_to_row::Dict{ConstrId,Int64} form::PresolveFormRepr deactivated_constrs::Vector{ConstrId} - fixed_vars::Dict{VarId,Int} + fixed_variables::Dict{VarId, Float64} end struct DwPresolveReform @@ -49,9 +49,11 @@ function create_presolve_form( lbs_vals = Float64[] ubs_vals = Float64[] + partial_sol = Float64[] for var in col_to_var push!(lbs_vals, getcurlb(form, var)) push!(ubs_vals, getcurub(form, var)) + push!(partial_sol, MathProg.get_value_in_partial_sol(form, var)) end rhs_vals = Float64[] @@ -67,12 +69,12 @@ function create_presolve_form( sense_vals, lbs_vals, ubs_vals, + partial_sol, lower_multiplicity, upper_multiplicity ) deactivated_constrs = ConstrId[] - fixed_vars = Dict{VarId,Float64}() return PresolveFormulation( col_to_var, @@ -81,18 +83,19 @@ function create_presolve_form( constr_to_row, form, deactivated_constrs, - fixed_vars, + Dict{VarId, Float64}() ) end function propagate_in_presolve_form( form::PresolveFormulation, rows_to_deactivate::Vector{Int}, - vars_to_fix::Dict{Int, Float64}, tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}} ) + fixed_vars = vars_to_fix(form.form, tightened_bounds) + col_mask = ones(Bool, form.form.nb_vars) - col_mask[collect(keys(vars_to_fix))] .= false + col_mask[collect(keys(fixed_vars))] .= false row_mask = ones(Bool, form.form.nb_constrs) row_mask[rows_to_deactivate] .= false @@ -107,19 +110,14 @@ function propagate_in_presolve_form( push!(deactivated_constrs, getid(constr)) end - fixed_vars = form.fixed_vars - for (col, val) in vars_to_fix - var_id = getid(form.col_to_var[col]) - @assert !haskey(fixed_vars, var_id) - fixed_vars[var_id] = val - end + form_repr = PresolveFormRepr(form.form, rows_to_deactivate, tightened_bounds, form.form.lower_multiplicity, form.form.upper_multiplicity) return PresolveFormulation( col_to_var, row_to_constr, var_to_col, constr_to_row, - PresolveFormRepr(form.form, rows_to_deactivate, vars_to_fix, tightened_bounds, form.form.lower_multiplicity, form.form.upper_multiplicity), + form_repr, deactivated_constrs, fixed_vars ) @@ -209,11 +207,12 @@ function update_form_from_presolve!(form::Formulation, presolve_form::PresolveFo deactivate!(form, getconstr(form, constr_id)) end - # Fix variables - for (var_id, val) in presolve_form.fixed_vars - # Do not propagate variable fixing! The new rhs of constraints is updated in the - # next step. - # TODO: fix!(form, getvar(form, var_id), val, false) + # Fixed variables + for (var_id, val) in presolve_form.fixed_variables + setcurlb!(form, var_id, 0.0) + setcurub!(form, var_id, 0.0) + MathProg.add_to_partial_solution!(form, var_id, val) + deactivate!(form, var_id) end # Update rhs @@ -229,6 +228,11 @@ function update_form_from_presolve!(form::Formulation, presolve_form::PresolveFo setcurlb!(form, presolve_form.col_to_var[col], lb) setcurub!(form, presolve_form.col_to_var[col], ub) end + + # Update partial solution + for (col, val) in enumerate(presolve_form.form.partial_solution) + MathProg.add_to_partial_solution!(form, presolve_form.col_to_var[col], val) + end return end diff --git a/src/Algorithm/presolve/propagation.jl b/src/Algorithm/presolve/propagation.jl index b93e936fd..02ce020a3 100644 --- a/src/Algorithm/presolve/propagation.jl +++ b/src/Algorithm/presolve/propagation.jl @@ -37,13 +37,13 @@ function propagate_var_bounds_from!(dest::PresolveFormulation, src::PresolveForm end # Look at fixed variable - common_var_ids = intersect(keys(src.fixed_vars), keys(dest.var_to_col)) + common_var_ids = intersect(keys(src.fixed_variables), keys(dest.var_to_col)) for var_id in common_var_ids - src_var_val = src.fixed_vars[var_id] + src_var_val = src.fixed_variables[var_id] dest_col = dest.var_to_col[var_id] - dest.form.lbs[dest_col] = src_var_val - dest.form.ubs[dest_col] = src_var_val + dest.form.lbs[dest_col] = src_var_val - dest.form.partial_solution[dest_col] + dest.form.ubs[dest_col] = src_var_val - dest.form.partial_solution[dest_col] end return end diff --git a/test/unit/Presolve/helpers.jl b/test/unit/Presolve/helpers.jl index ef89d0aab..c313ce87d 100644 --- a/test/unit/Presolve/helpers.jl +++ b/test/unit/Presolve/helpers.jl @@ -50,9 +50,9 @@ function test_presolve_builder1() sense = [Less, Greater, Equal, Greater, Less, Equal] lbs = [1, 0, 2, 1, -1, -Inf, 0] ubs = [10, Inf, 3, 2, 1, 0, 1] + partial_sol = zeros(Float64, length(lbs)) - - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1.0, 1.0) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1.0, 1.0) @test form.nb_vars == 7 @test form.nb_constrs == 6 @test all(form.col_major_coef_matrix .== coef_matrix) @@ -79,22 +79,23 @@ function test_presolve_builder2() sense = [Less, Greater, Equal, Greater, Less, Equal] lbs = [1, 0, 2, 1, -1, -Inf, 0] ubs = [10, Inf, 3, 2, 1, 0, 1] + partial_sol = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1, 1) # Deactivate some rows. rows_to_deactivate = [1, 3, 6] - vars_to_fix = Dict{Int, Float64}() tightened_bounds = Dict{Int, Tuple{Float64, Bool, Float64, Bool}}() - form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, vars_to_fix, 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], :]) - @test all(form2.rhs .== rhs[[2, 4, 5]]) + @test all(form2.rhs .== rhs[[2, 4, 5]] - [1*2 - 1, 2*2 - 4, 2*2 - 4]) @test all(form2.sense .== sense[[2, 4, 5]]) - @test all(form2.lbs .== lbs) - @test all(form2.ubs .== ubs) + @test all(form2.lbs .== [0, 0, 0, 0, -1, -Inf, 0]) + @test all(form2.ubs .== [9, Inf, 1, 1, 1, 0, 1]) + @test all(form2.partial_solution .== [1, 0, 2, 1, 0, 0, 0]) end register!(unit_tests, "presolve_helper", test_presolve_builder2) @@ -113,14 +114,15 @@ function test_presolve_builder3() sense = [Less, Greater, Equal, Greater, Less, Equal] lbs = [10, 2, 1, 1, -1, 0, -1] ubs = [10, 3, 1, 2, 1, 0, -1] + partial_sol = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1, 1) # Deactivate some rows. rows_to_deactivate = Int[] - vars_to_fix = Dict{Int, Float64}(1 => 10, 3 => 1, 6 => 0, 7 => -1) tightened_bounds = Dict{Int,Tuple{Float64, Bool, Float64, Bool}}() + # Fixed variables: # -1 - 2.5 # <= 4 -> 7.5 # 1 + 2.5 # >= -4 -> -7.5 # 10 # == 1 -> -9 @@ -128,14 +130,24 @@ function test_presolve_builder3() # 2 # <= 1 -> -1 # 10+ 3 -1 # == 6 -> -6 - form2 = Coluna.Algorithm.PresolveFormRepr(form, rows_to_deactivate, vars_to_fix, tightened_bounds, 1.0, 1.0) + # Lower bound reduction: + # x2 = 2, x4 = 1 + # <= 7.5 - 1 -> 6.5 + # >= -7.5 + 1 -> -6.5 + # == -9 -> -9 + # >= 0 - 2 + 4 -> 2 + # <= -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) @test form2.nb_vars == 3 @test form2.nb_constrs == 6 @test all(form2.col_major_coef_matrix .== coef_matrix[:, [2, 4, 5]]) - @test all(form2.rhs .== [7.5, -7.5, -9, 0, -1, -6]) + @test all(form2.rhs .== [6.5, -6.5, -9, 2, 1, -7.5]) @test all(form2.sense .== sense) - @test all(form2.lbs .== lbs[[2, 4, 5]]) - @test all(form2.ubs .== ubs[[2, 4, 5]]) + @test all(form2.lbs .== [0, 0, -1]) # Vars 2, 4 & 5 + @test all(form2.ubs .== [1, 1, 1]) # Vars 2, 4, & 5 + @test all(form2.partial_solution .== [2, 1, 0]) end register!(unit_tests, "presolve_helper", test_presolve_builder3) @@ -154,30 +166,63 @@ function test_presolve_builder4() sense = [Less, Greater, Equal, Greater, Less, Equal] lbs = [1, 0, 2, 1, -1, -Inf, 0] ubs = [10, Inf, 3, 2, 1, 0, 1] + partial_sol = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1.0, 1.0) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1.0, 1.0) rows_to_deactivate = Int[] - vars_to_fix = Dict{Int,Float64}() tightened_bounds = Dict{Int,Tuple{Float64, Bool, Float64, Bool}}( 1 => (1, false, 2, true), 2 => (0, true, 1, true), 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, vars_to_fix, tightened_bounds, 1.0, 1.0) - @test form2.nb_vars == 7 + 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) - @test all(form2.rhs .== rhs) + @test all(form2.col_major_coef_matrix .== coef_matrix[:, [1, 2, 3, 4, 5, 7]]) + @test all(form2.rhs .== [4.5, -4.5, 0.0, 2.0, 1.0, -7.0]) @test all(form2.sense .== sense) - @test all(form2.lbs .== [1, 0, 2, 1, -1, 0.5, 0]) - @test all(form2.ubs .== [2, 1, 3, 2, 1, 0.5, 1]) + @test all(form2.lbs .== [0, 0, 0, 0, -1, 0]) + @test all(form2.ubs .== [1, 1, 1, 1, 1, 1]) + @test all(form2.partial_solution .== [1, 0, 2, 1, 0, 0]) end register!(unit_tests, "presolve_helper", test_presolve_builder4) function test_presolve_builder5() + # 2x1 + 3x2 - 2x3 >= 2 + # 3x1 - 4x2 + x3 >= 5 + + coef_matrix = sparse([ + 2 3 -2 + 3 -4 1 + ]) + rhs = [2, 5] + sense = [Greater, Greater] + lbs = [0, 0, -3] + ubs = [Inf, Inf, 3] + partial_sol = [1, 1, 0] + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1, 1) + rows_to_deactivate = Int[] + tightened_bounds = Dict{Int,Tuple{Float64, Bool, Float64, Bool}}( + 1 => (1, true, Inf, false), + 2 => (1, true, Inf, false), + 3 => (1, true, 2, true) + ) + + 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) + @show form2.rhs + @test all(form2.rhs .== ([2, 5] - [2 + 3 - 2, 3 - 4 + 1])) + @test all(form2.sense .== sense) + @show form2.lbs + @show form2.ubs + @test all(form2.lbs .== [0, 0, 0]) + @test all(form2.ubs .== [Inf, Inf, 1]) + @test all(form2.partial_solution .== [2, 2, 1]) end register!(unit_tests, "presolve_helper", test_presolve_builder5) @@ -195,8 +240,9 @@ function row_activity() sense = [Less, Greater, Equal, Greater, Less, Equal] lbs = [1, 0, 2, 1, -1, -Inf, 0] ubs = [10, Inf, 3, 2, 1, 0, 1] + partial_sol = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1, 1) @test Coluna.Algorithm.row_min_activity(form, 1) == 0 + 0 - 1 * ubs[3] + 1 * lbs[4] + 0 + 1 * lbs[6] + 2.5 * lbs[7] @test Coluna.Algorithm.row_max_activity(form, 1) == 0 + 0 - 1 * lbs[3] + 1 * ubs[4] + 0 + 1 * ubs[6] + 2.5 * ubs[7] @@ -227,8 +273,9 @@ function row_slack() sense = [Less, Greater, Equal, Greater, Less, Equal] lbs = [1, 0, 2, 1, -1, -Inf, 0] ubs = [10, Inf, 3, 2, 1, 0, 1] + partial_sol = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_sol, 1, 1) @test Coluna.Algorithm.row_min_slack(form, 1) == rhs[1] - Coluna.Algorithm.row_max_activity(form, 1) # ok @test Coluna.Algorithm.row_max_slack(form, 1) == rhs[1] - Coluna.Algorithm.row_min_activity(form, 1) # ok @@ -370,8 +417,9 @@ function test_var_bounds_from_row1() sense = [Greater; Less] lbs = [0, 0, 0] ubs = [10, 2, 1] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) @@ -408,8 +456,9 @@ function test_var_bounds_from_row2() sense = [Less, Greater] lbs = [0, 0, 0] ubs = [10, 1, 1] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) @@ -446,8 +495,9 @@ function test_var_bounds_from_row3() ubs = [10, 8, 1] rhs = [9, -9] sense = [Less, Greater] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) @@ -484,8 +534,9 @@ function test_var_bounds_from_row4() ubs = [0, 2, 2] rhs = [10, -10] sense = [Greater, Less] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) @@ -528,8 +579,9 @@ function test_var_bounds_from_row5() ubs = [3, 1, 1] rhs = [5] sense = [Equal] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) @@ -558,8 +610,9 @@ function test_var_bounds_from_row6() ubs = [0.5, Inf, 0.3, Inf] sense = [Greater, Greater] rhs = [1, 1] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack1 = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 2) max_slack1 = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 2) @@ -593,8 +646,9 @@ function test_var_bounds_from_row7() ubs = [10, Inf, Inf] rhs = [150, 600] sense = [Greater, Less] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) @@ -637,8 +691,9 @@ function test_var_bounds_from_row8() # this was producing a bug ubs = [2, Inf, Inf] rhs = [1] sense = [Less] + partial_solution = zeros(Float64, length(lbs)) - form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, 1, 1) + form = Coluna.Algorithm.PresolveFormRepr(coef_matrix, rhs, sense, lbs, ubs, partial_solution, 1, 1) min_slack = Coluna.Algorithm.row_min_slack(form, 1, col -> col == 1) max_slack = Coluna.Algorithm.row_max_slack(form, 1, col -> col == 1) diff --git a/test/unit/Presolve/propagation.jl b/test/unit/Presolve/propagation.jl index 27597835d..55557f08e 100644 --- a/test/unit/Presolve/propagation.jl +++ b/test/unit/Presolve/propagation.jl @@ -71,6 +71,7 @@ function _presolve_formulation(var_names, constr_names, matrix, form, name_to_va sense = [Coluna.MathProg.getcursense(form, name_to_constrs[name]) for name in constr_names] lbs = [Coluna.MathProg.getcurlb(form, name_to_vars[name]) for name in var_names] ubs = [Coluna.MathProg.getcurub(form, name_to_vars[name]) for name in var_names] + partial_solution = zeros(Float64, length(lbs)) form_repr = Coluna.Algorithm.PresolveFormRepr( matrix, @@ -78,6 +79,7 @@ function _presolve_formulation(var_names, constr_names, matrix, form, name_to_va sense, lbs, ubs, + partial_solution, lm, um ) @@ -94,7 +96,7 @@ function _presolve_formulation(var_names, constr_names, matrix, form, name_to_va constr_to_row, form_repr, Coluna.MathProg.ConstrId[], - Dict{Coluna.MathProg.VarId,Float64}() + Dict{Coluna.MathProg.VarId, Float64}() ) return presolve_form end @@ -677,14 +679,9 @@ function test_var_fixing_propagation_within_formulation1() @test bounds_result[2] == (0.0, false, 11.0, true) @test bounds_result[3] == (0.0, false, 11.0, true) - result = Coluna.Algorithm.vars_to_fix(orig_presolve_form.form, bounds_result) - @test result[1] == 2.0 - @test length(result) == 1 - new_form = Coluna.Algorithm.propagate_in_presolve_form( orig_presolve_form, Int[], - result, bounds_result ) @@ -706,8 +703,8 @@ function test_var_fixing_propagation_within_formulation1() @test length(new_form.deactivated_constrs) == 0 - @test new_form.fixed_vars[ClMP.getid(orig_name_to_var["x"])] == 2.0 - @test length(new_form.fixed_vars) == 1 + @test new_form.fixed_variables[ClMP.getid(orig_name_to_var["x"])] == 2.0 + @test length(new_form.fixed_variables) == 1 end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation1) @@ -743,13 +740,10 @@ function test_var_fixing_propagation_within_formulation2() bounds_result = Coluna.Algorithm.bounds_tightening(orig_presolve_form.form) @test isempty(bounds_result) - result = Coluna.Algorithm.vars_to_fix(orig_presolve_form.form, bounds_result) - @test result[1] == 4.0 - new_form = Coluna.Algorithm.propagate_in_presolve_form( orig_presolve_form, Int[], - result, + #result, bounds_result ) @@ -771,8 +765,8 @@ function test_var_fixing_propagation_within_formulation2() @test length(new_form.deactivated_constrs) == 0 - @test new_form.fixed_vars[ClMP.getid(orig_name_to_var["x"])] == 4.0 - @test length(new_form.fixed_vars) == 1 + @test new_form.fixed_variables[ClMP.getid(orig_name_to_var["x"])] == 4.0 + @test length(new_form.fixed_variables) == 1 end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation2) @@ -812,13 +806,9 @@ function test_var_fixing_propagation_within_formulation3() @test bounds_result[3] == (0.0, false, 610.0, true) @test length(bounds_result) == 2 - result = Coluna.Algorithm.vars_to_fix(orig_presolve_form.form, bounds_result) - @test result[1] == 10.0 - new_form = Coluna.Algorithm.propagate_in_presolve_form( orig_presolve_form, Int[], - result, bounds_result ) @@ -841,8 +831,8 @@ function test_var_fixing_propagation_within_formulation3() @test length(new_form.deactivated_constrs) == 0 - @test new_form.fixed_vars[ClMP.getid(orig_name_to_var["x"])] == 10.0 - @test length(new_form.fixed_vars) == 1 + @test new_form.fixed_variables[ClMP.getid(orig_name_to_var["x"])] == 10.0 + @test length(new_form.fixed_variables) == 1 end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation3) @@ -930,10 +920,6 @@ function test_var_fixing_propagation_from_original_to_master() bounds_result = Coluna.Algorithm.bounds_tightening(orig_presolve_form.form) @test bounds_result[1] == (0, false, 0, true) @test length(bounds_result) == 1 - result = Coluna.Algorithm.vars_to_fix(orig_presolve_form.form, bounds_result) - @test result[1] == 0 - @test result[2] == 1 - @test length(result) == 2 end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_master) @@ -1048,10 +1034,6 @@ function test_var_fixing_propagation_from_master_to_subproblem1() # Run the presolve variable fixing on the original formulation. bounds_result = Coluna.Algorithm.bounds_tightening(master_repr_presolve_form.form) @test isempty(bounds_result) - result = Coluna.Algorithm.vars_to_fix(master_repr_presolve_form.form, bounds_result) - @test result[1] == 0 - @test result[4] == 1 - @test length(result) == 2 return end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem1) @@ -1165,13 +1147,11 @@ function test_var_fixing_propagation_from_master_to_subproblem2() # Run the presolve variable fixing on the original formulation. bounds_result = Coluna.Algorithm.bounds_tightening(master_repr_presolve_form.form) @test isempty(bounds_result) - result = Coluna.Algorithm.vars_to_fix(master_repr_presolve_form.form, bounds_result) - @test result[1] == 0 - @test result[4] == 1 - @test length(result) == 2 new_master_repr_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( - master_repr_presolve_form, Int[], result, bounds_result + master_repr_presolve_form, + Int[], + bounds_result ) # Propagate bounds in subproblems @@ -1194,16 +1174,9 @@ function test_var_fixing_propagation_from_master_to_subproblem2() # Fixing variables in subproblems sp1_bounds_result = Coluna.Algorithm.bounds_tightening(sp1_presolve_form.form) @test sp1_bounds_result == Dict(2 => (1, true, 1, false)) - sp1_result = Coluna.Algorithm.vars_to_fix(sp1_presolve_form.form, sp1_bounds_result) - @test sp1_result[1] == 0 - @test sp1_result[2] == 1 - @test length(sp1_result) == 2 sp2_bounds_result = Coluna.Algorithm.bounds_tightening(sp2_presolve_form.form) @test isempty(sp2_bounds_result) - sp2_result = Coluna.Algorithm.vars_to_fix(sp2_presolve_form.form, sp2_bounds_result) - @test sp2_result[2] == 1 - @test length(sp2_result) == 1 return end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem2) @@ -1292,47 +1265,52 @@ function update_master_repr_formulation() DynamicSparseArrays.closefillmode!(master_form_coef_matrix) master_repr_presolve_form = _presolve_formulation( - ["x1", "x2", "y1", "y2"], ["c1", "c2"], [1 1 1 1; 2 1 3 3;], master_form, master_name_to_var, master_name_to_constr + ["x1", "x2", "y1", "y2"], + ["c1", "c2"], + [1 1 1 1; 2 1 3 3;], + master_form, + master_name_to_var, + master_name_to_constr ) updated_master_repr_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( master_repr_presolve_form, Int[2], - Dict(1 => 1.0), Dict(1 => (1.0, true, 1.0, false), 2 => (0.1, true, 0.5, true)) ) @test updated_master_repr_presolve_form.form.col_major_coef_matrix == [1 1 1;] - @test updated_master_repr_presolve_form.form.rhs == [3] + @test updated_master_repr_presolve_form.form.rhs == [4 - 1 - 0.1] @test updated_master_repr_presolve_form.form.sense == [ClMP.Greater] - @test updated_master_repr_presolve_form.form.lbs == [0.1, 0.0, 0.0] - @test updated_master_repr_presolve_form.form.ubs == [0.5, 1.0, 1.0] + @test updated_master_repr_presolve_form.form.lbs == [0.0, 0.0, 0.0] + @test updated_master_repr_presolve_form.form.ubs == [0.4, 1.0, 1.0] Coluna.Algorithm.update_form_from_presolve!(master_form, updated_master_repr_presolve_form) vars = [ - # name, lb, ub, fixed - ("x1", 1.0, 1.0, true), - ("x2", 0.1, 0.5, false), - ("y1", 0.0, 1.0, false), - ("y2", 0.0, 1.0, false), - ("MC1", 0.0, 1.0, false), - ("MC2", 0.0, 1.0, false), - ("MC3", 0.0, 1.0, false), - ("MC4", 0.0, 1.0, false), - ("a1", 0.0, Inf, false), - ("a2", 0.0, Inf, false) + # name, lb, ub, partial_sol_value, deactivated + ("x1", 0.0, 0.0, 1.0, true), + ("x2", 0.0, 0.4, 0.1, false), + ("y1", 0.0, 1.0, 0.0, false), + ("y2", 0.0, 1.0, 0.0, false), + ("MC1", 0.0, 1.0, 0.0, false), + ("MC2", 0.0, 1.0, 0.0, false), + ("MC3", 0.0, 1.0, 0.0, false), + ("MC4", 0.0, 1.0, 0.0, false), + ("a1", 0.0, Inf, 0.0, false), + ("a2", 0.0, Inf, 0.0, false) ] - # for (var_name, lb, ub, fixed) in vars - # var = master_name_to_var[var_name] - # @test ClMP.getcurlb(master_form, var) == lb - # @test ClMP.getcurub(master_form, var) == ub - # # @test ClMP.isfixed(master_form, var) == fixed - # end + for (var_name, lb, ub, partial_sol_value, deactivated) in vars + var = master_name_to_var[var_name] + @test ClMP.getcurlb(master_form, var) == lb + @test ClMP.getcurub(master_form, var) == ub + @test ClMP.get_value_in_partial_sol(master_form, var) == partial_sol_value + @test ClMP.iscuractive(master_form, var) == !deactivated + end constrs = [ - ("c1", ClMP.Greater, 3.0), + ("c1", ClMP.Greater, 2.9), ] for (constr_name, sense, rhs) in constrs constr = master_name_to_constr[constr_name]