From 7f422c85c1f10f7b154fbefbe5d96dd6bffa57d9 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 20 Oct 2023 16:43:59 +0200 Subject: [PATCH 1/7] partial sol propagation refactoring --- src/Algorithm/presolve/interface.jl | 241 +++++++++++++++++--------- src/Algorithm/presolve/partial_sol.md | 46 +++++ src/Algorithm/presolve/propagation.jl | 58 +------ test/unit/Presolve/propagation.jl | 84 ++++++--- 4 files changed, 264 insertions(+), 165 deletions(-) create mode 100644 src/Algorithm/presolve/partial_sol.md diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index 45996286b..1a9623c39 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -250,10 +250,10 @@ function create_presolve_reform(reform::Reformulation{DwMaster}) return DwPresolveReform(original_master, restricted_master, dw_sps) end -function _update_partial_sol!(form::Formulation{DwMaster}, presolve_form::PresolveFormulation) +function update_partial_sol!(form::Formulation{DwMaster}, presolve_form::PresolveFormulation, partial_solution) # Update partial solution partial_sol_counter = 0 - for (col, val) in enumerate(presolve_form.form.partial_solution) + for (col, val) in enumerate(partial_solution) var = presolve_form.col_to_var[col] if getduty(getid(var)) <= MasterArtVar && !iszero(val) error(""" Infeasible because artificial variable $(getname(form, var)) is not zero. @@ -263,13 +263,12 @@ function _update_partial_sol!(form::Formulation{DwMaster}, presolve_form::Presol if !iszero(val) MathProg.add_to_partial_solution!(form, var, val) partial_sol_counter += 1 + setcurlb!(form, var, 0.0) end end return end -_update_partial_sol!(form, presolve_form) = nothing - function _update_bounds!(form::Formulation, presolve_form::PresolveFormulation) # Update bounds for (col, (lb, ub)) in enumerate(Iterators.zip( @@ -292,20 +291,6 @@ function _update_bounds!(form::Formulation, presolve_form::PresolveFormulation) return end -# function _update_convexity_constr!(master::Formulation{DwMaster}, sp_presolve_form, sp::Formulation{DwSp}) -# lm_constr = getconstr(master, sp.duty_data.lower_multiplicity_constr_id) -# um_constr = getconstr(master, sp.duty_data.upper_multiplicity_constr_id) - -# @assert !isnothing(lm_constr) && !isnothing(um_constr) - -# lm = sp_presolve_form.form.lower_multiplicity -# um = sp_presolve_form.form.upper_multiplicity - -# setcurrhs!(master, lm_constr, max(lm, 0)) -# setcurrhs!(master, um_constr, max(um, 0)) -# return -# end - function _update_rhs!(form::Formulation, presolve_form::PresolveFormulation) for (row, rhs) in enumerate(presolve_form.form.rhs) constr = presolve_form.row_to_constr[row] @@ -322,11 +307,7 @@ function _update_rhs!(form::Formulation, presolve_form::PresolveFormulation) return end -function update_form_from_presolve!( - form::Formulation, presolve_form::PresolveFormulation; - update_rhs = true, - update_partial_sol = true, -) +function update_form_from_presolve!(form::Formulation, presolve_form::PresolveFormulation) # Deactivate Constraints constr_deactivation_counter = 0 for constr_id in presolve_form.deactivated_constrs @@ -348,15 +329,8 @@ function update_form_from_presolve!( end end - if update_rhs - _update_rhs!(form, presolve_form) - end - + _update_rhs!(form, presolve_form) _update_bounds!(form, presolve_form) - - if update_partial_sol - _update_partial_sol!(form, presolve_form) - end return end @@ -365,10 +339,6 @@ function update_reform_from_presolve!( dw_pricing_sps::Dict, presolve_reform::DwPresolveReform ) - # Update master - presolve_restr_master = presolve_reform.restricted_master - update_form_from_presolve!(master, presolve_restr_master) - # Update subproblems for (spid, sp) in dw_pricing_sps sp_presolve_form = presolve_reform.dw_sps[spid] @@ -376,11 +346,7 @@ function update_reform_from_presolve!( end presolve_repr_master = presolve_reform.original_master - update_form_from_presolve!( - master, presolve_repr_master; - update_partial_sol = false, - update_rhs = false - ) + update_form_from_presolve!(master, presolve_repr_master) return end @@ -416,40 +382,134 @@ struct PresolveOutput feasible::Bool end -function propagate_partial_sol_into_master(presolve_reform, dw_pricing_sps) - # Step 1 (before main loop): - # Create the 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 +function _get_partial_sol(presolve_form_repr::PresolveFormRepr) + new_partial_sol = zeros(Float64, length(presolve_form_repr.partial_solution)) + for (i, (lb, ub)) in enumerate(Iterators.zip(presolve_form_repr.lbs, presolve_form_repr.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 + return new_partial_sol +end + +get_restr_partial_sol(presolve_form) = _get_partial_sol(presolve_form.form) + +function compute_rhs(presolve_form, restr_partial_sol) + rhs = presolve_form.form.rhs + coef_matrix = presolve_form.form.col_major_coef_matrix + return rhs - coef_matrix * restr_partial_sol +end + +function update_subproblem_multiplicities!(dw_sps, nb_fixed_columns_per_sp) + for (spid, presolve_sp) in dw_sps + lm = presolve_sp.form.lower_multiplicity + um = presolve_sp.form.upper_multiplicity + + presolve_sp.form.lower_multiplicity = max(0, lm - nb_fixed_columns_per_sp[spid]) + presolve_sp.form.upper_multiplicity = max(0, um - nb_fixed_columns_per_sp[spid]) # TODO if < 0 -> error + end + return +end + +function propagate_partial_sol_to_global_bounds!(presolve_repr_master, local_repr_partial_sol, default_global_bounds) + new_lbs = zeros(Float64, presolve_repr_master.form.nb_vars) + new_ubs = zeros(Float64, presolve_repr_master.form.nb_vars) + + for (col, (val, lb, ub, (def_glob_lb, def_glob_ub))) in enumerate( + Iterators.zip( + local_repr_partial_sol, + presolve_repr_master.form.lbs, + presolve_repr_master.form.ubs, + default_global_bounds + ) ) - presolve_reform.restricted_master = new_restr_master + new_lbs[col] = max(lb - val, def_glob_lb) + new_ubs[col] = min(ub - val, def_glob_ub) + end - # Step 2: Propagate the partial solution to the local bounds. - propagate_partial_sol_to_repr_master!( - dw_pricing_sps, - presolve_reform + presolve_repr_master.form.lbs = new_lbs + presolve_repr_master.form.ubs = new_ubs + return +end + +function compute_default_global_bounds(presolve_repr_master, presolve_dw_pricing_sps, master, dw_pricing_sps) + global_bounds = Dict{VarId, Tuple{Float64,Float64}}() + + for (spid, sp) in dw_pricing_sps + lm = presolve_dw_pricing_sps[spid].form.lower_multiplicity + um = presolve_dw_pricing_sps[spid].form.upper_multiplicity + + # Update bounds on master repr variables using multiplicity. + for (varid, var) in getvars(sp) + if getduty(varid) <= DwSpPricingVar + lb = getcurlb(sp, var) + ub = getcurub(sp, var) + + (global_lb, global_ub) = get(global_bounds, varid, (0.0, 0.0)) + global_lb += (lb > 0 ? lm : um) * lb + global_ub += (ub > 0 ? um : lm) * ub + + global_bounds[varid] = (global_lb, global_ub) + end + end + end + + master_repr_var_bounds = [(-Inf, Inf) for _ in 1:presolve_repr_master.form.nb_vars] + for (varid, bounds) in global_bounds + col = get(presolve_repr_master.var_to_col, varid, nothing) + @assert !isnothing(col) + master_repr_var_bounds[col] = bounds + end + return master_repr_var_bounds +end + +function propagate_partial_sol_into_master(presolve_reform, master, dw_pricing_sps) + presolve_restricted_master = presolve_reform.restricted_master + + # Step 1: create the local partial solution from the restricted master presolve representation. + # This local partial solution must then be "fixed" & propagated. + local_restr_partial_sol = get_restr_partial_sol(presolve_restricted_master) + + # Step 2: compute the rhs of all constraints. + # Non-robust and convexity constraints rhs can only be computed using this representation. + rhs = compute_rhs(presolve_restricted_master, local_restr_partial_sol) + + # Step 3: project local partial solution on the representative master. + local_repr_partial_sol, nb_fixed_columns_per_sp = partial_sol_on_repr( + dw_pricing_sps, presolve_reform, local_restr_partial_sol ) - new_repr_master = propagate_in_presolve_form( + # Step 4: update the multiplicity of each subproblem. + update_subproblem_multiplicities!(presolve_reform.dw_sps, nb_fixed_columns_per_sp) + + # Step 5: compute new default global bounds + master_repr_default_global_bounds = compute_default_global_bounds( + presolve_reform.original_master, presolve_reform.dw_sps, master, dw_pricing_sps + ) + + # Step 6: propagate local partial solution from the representative master representation + # into the global bounds. + propagate_partial_sol_to_global_bounds!( presolve_reform.original_master, - Int[], - Dict{Int, Tuple{Float64, Bool, Float64, Bool}}(); - tighten_bounds = false, - shrink = false, - store_unpropagated_partial_sol = false + local_repr_partial_sol, + master_repr_default_global_bounds ) - presolve_reform.original_master = new_repr_master - # The right rhs is the one from the restricted master because the master may - # contain non-robust cuts. + # Step 7: Update the rhs of the representative master. @assert length(presolve_reform.restricted_master.form.rhs) == length(presolve_reform.original_master.form.rhs) for (row, rhs) in enumerate(presolve_reform.restricted_master.form.rhs) presolve_reform.original_master.form.rhs[row] = rhs end + return local_restr_partial_sol end function presolve_iteration!(presolve_reform, master, dw_pricing_sps) @@ -465,27 +525,41 @@ function presolve_iteration!(presolve_reform, master, dw_pricing_sps) new_repr_master = propagate_in_presolve_form( presolve_reform.original_master, Int[], - - tightened_bounds_repr; shrink = false + tightened_bounds_repr; + store_unpropagated_partial_sol = false ) presolve_reform.original_master = new_repr_master + return +end + +function _presolve_run!(presolve_reform, master, dw_pricing_sps) + local_restr_partial_sol = propagate_partial_sol_into_master( + presolve_reform, + master, + dw_pricing_sps + ) - # Step 6: Shrink the formulation (remove fixed variables). - new_restr_master = propagate_in_presolve_form( + for i in 1:3 + println("**** Presolve step $i ****") + presolve_iteration!(presolve_reform, master, dw_pricing_sps) + end + + update_partial_sol!( + master, presolve_reform.restricted_master, - Int[], - Dict{Int, Tuple{Float64, Bool, Float64, Bool}}(); - tighten_bounds = false, - partial_sol = false + local_restr_partial_sol ) - - presolve_reform.restricted_master = new_restr_master - return + + update_reform_from_presolve!( + master, + dw_pricing_sps, + presolve_reform + ) + return end function run!(algo::PresolveAlgorithm, ::Env, reform::Reformulation, input::PresolveInput)::PresolveOutput - # Should be move in the diving (when generating the formulation of the children because # formulation is the single source of truth). for (varid, val) in input.partial_sol_to_fix @@ -498,18 +572,13 @@ function run!(algo::PresolveAlgorithm, ::Env, reform::Reformulation, input::Pres end presolve_reform = create_presolve_reform(reform) + master = getmaster(reform) + dw_pricing_sps = get_dw_pricing_sps(reform) - propagate_partial_sol_into_master(presolve_reform, get_dw_pricing_sps(reform)) - - for i in 1:3 - println("**** Presolve step $i ****") - presolve_iteration!(presolve_reform, getmaster(reform), get_dw_pricing_sps(reform)) - end - - update_reform_from_presolve!( - getmaster(reform), - get_dw_pricing_sps(reform), - presolve_reform + _presolve_run!( + presolve_reform, + master, + dw_pricing_sps ) return PresolveOutput(true) end diff --git a/src/Algorithm/presolve/partial_sol.md b/src/Algorithm/presolve/partial_sol.md new file mode 100644 index 000000000..fd45fd027 --- /dev/null +++ b/src/Algorithm/presolve/partial_sol.md @@ -0,0 +1,46 @@ +# Augmenting the partial solution in _Coluna_: pre-processing + +>### Ruslan Sadykov, 19/10/2023 + +Consider a _local partial solution_ $(\bar{\bm x}^{\rm pure}, \bar{\bm\lambda})$ (where $\bar{\bm x}^{\rm pure}$ is the vector of values for pure master variables, and $\bar{\bm\lambda}$ is the vector of values for master columns), which should be added to the _global partial solution_ $(\bar{\bm y}^{\rm pure}, \bar{\bm\theta})$. + +First, we augment the global partial solution: $(\bar{\bm y}^{\rm pure}, \bar{\bm\theta})\leftarrow(\bar{\bm x}^{\rm pure}+\bar{\bm y}^{\rm pure}, \bar{\bm\lambda}+\bar{\bm\theta})$. + +Second, we update the right-hand side values of the master constraints: ${\rm rhs}_i\leftarrow {\rm rhs}_i - {\bm A}^{\rm pure}\cdot\bar{\bm x}^{\rm pure} - {\bm A}^{\rm col}\cdot\bar{\bm \lambda}$, where ${\bm A}^{\rm pure}$ is the matrix of coefficients of pure master constraints and ${\bm A}^{\rm col}$ is the matrix of coefficients of master columns. + +Third, we update subproblem multiplicities $U_k\leftarrow U_k - \sum_{q\in Q_k}\bar\lambda_q$, and $L_k\leftarrow \max\left\{0,\; L_k - \sum_{q\in Q_k}\bar\lambda_q\right\}$, where $Q_k$ is the set of indices of columns associated with solutions from subproblem $k$. + +Afterwards, we should update the bounds of pure master variables and representative master variables. For that we first calculate the representative local partial solution: $\bar{\bm x}^{\rm repr} = \sum_{q\in Q}{\bm s_q}\cdot \bar\lambda_q$, where $Q$ is the total number of columns, and ${\bm s_q}$ is the subproblem solution associated with column $\lambda_q$. Update of bounds of variables can be performed one variable at a time. This is presented in the next two sections. + +## Pure master variables + +Consider a pure master variable $x^{\rm pure}_j$ with $\bar{x}^{\rm pure}_j\neq 0$ and bounds $[lb_j,ub_j]$ before augmenting the partial solution. + +If $\bar{x}^{\rm pure}_j > 0$, then we have $lb_j\leftarrow 0$, $ub_j\leftarrow ub_j - \bar{x}^{\rm pure}_j$. + +If $\bar{x}^{\rm pure}_j < 0$, then we have $lb_j\leftarrow lb_j - \bar{x}^{\rm pure}_j$, $ub_j\leftarrow 0$. + + +## Representative variables + +We consider a representative master variable $x^{\rm repr}_j$ with bounds $[lb^g_j, ub^g_j]$ before augmenting the partial solution. We assume that $x^{\rm repr}_j$ represents exactly one variable $x^k_j$ in subproblem $k$ with bounds $[lb^l_j, ub^l_j]$ before augmenting the partial solution. _This assumption should be verified before augmenting the partial solution!_ For the clarity of presentation, we omit index $j$ for the remainder of this +section. + +After augmenting the partial solution, the following inequalities should be satisfied: +$$ lb^g - \bar{x}^{\rm repr}\leq x^{\rm repr} \leq ub^g - \bar{x}^{\rm repr}.$$ + +At the same time, we should have +$$\min\{lb^l\cdot L_k,\; lb^l\cdot U_k\}\leq x^{\rm repr}\leq \max\{ub^l\cdot U_k,\; ub^l\cdot L_k\}$$ + +Thus, the following update should be done +$$ lb^g\leftarrow \max\left\{lb^g - \bar{x}^{\rm repr},\; \min\{lb^l\cdot L_k,\; lb^l\cdot U_k\}\right\}$$ +$$ ub^g\leftarrow \min\left\{ub^g - \bar{x}^{\rm repr},\; \max\{ub^l\cdot U_k,\; ub^l\cdot L_k\}\right\}$$ + +> _**Example 1:**_ $0\leq x^k\leq 3$, $0\leq x^{\rm repr}\leq 6$, $L_k=0$, $U_k=2$. Let $\bar{x}^{\rm repr}=2$. Then after augmenting the partial solution, we have +> $$ \max\left\{-2,\; 0\right\}\leq x^{\rm repr} \leq \min\left\{4,\; 3\right\} \Rightarrow 0 \leq x^{\rm repr} \leq 3$$ + +> _**Example 2:**_ $0\leq x^k\leq 5$, $3\leq x^{\rm repr}\leq 6$, $L_k=0$, $U_k=2$. Let $\bar{x}^{\rm repr}=2$. Then after augmenting the partial solution, we have +> $$ \max\left\{1,\; 0\right\}\leq x'_{\rm repr} \leq \min\left\{4,\; 5\right\} \Rightarrow 1 \leq x'_{\rm repr} \leq 4$$ + +> _**Example 3:**_ $-1\leq x^k\leq 4$, $-2\leq x^{\rm repr}\leq 2$, $L_k=0$, $U_k=2$. Let $\bar{x}^{\rm repr}=-1$. Then after augmenting the partial solution, we have +> $$ \max\left\{-1,\; -1\right\}\leq x^{\rm repr} \leq \min\left\{3,\; 4\right\} \Rightarrow -1 \leq x^{\rm repr} \leq 3$$ \ No newline at end of file diff --git a/src/Algorithm/presolve/propagation.jl b/src/Algorithm/presolve/propagation.jl index 3fe2d3657..adf5af4b7 100644 --- a/src/Algorithm/presolve/propagation.jl +++ b/src/Algorithm/presolve/propagation.jl @@ -102,30 +102,6 @@ function propagate_global_to_local_bounds!( return end -function _get_local_lb(global_lb, prec_local_lb, lm, um) - if global_lb < 0 - return max(global_lb, prec_local_lb) - elseif global_lb > 0 - return min(global_lb, prec_local_lb) - end - if prec_local_lb < 0 && iszero(um) || prec_local_lb > 0 && iszero(lm) - return prec_local_lb - end - return 0.0 -end - -function _get_local_ub(global_ub, prec_local_ub, lm, um) - if global_ub < 0 - return max(global_ub, prec_local_ub) - elseif global_ub > 0 - return min(global_ub, prec_local_ub) - end - if prec_local_ub < 0 && iszero(lm) || prec_local_ub > 0 && iszero(um) - return prec_local_ub - end - return 0.0 -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 @@ -135,10 +111,8 @@ function propagate_local_bounds!(presolve_repr_master::PresolveFormulation, mast if !isnothing(repr_col) global_lb = presolve_repr_master.form.lbs[repr_col] global_ub = presolve_repr_master.form.ubs[repr_col] - prec_local_lb = presolve_sp.form.lbs[i] - prec_local_ub = presolve_sp.form.ubs[i] - local_lb = _get_local_lb(global_lb, prec_local_lb, lm, um) - local_ub = _get_local_ub(global_ub, prec_local_ub, lm, um) + 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) @@ -185,7 +159,8 @@ end function partial_sol_on_repr( dw_pricing_sps::Dict, - presolve_reform_repr::DwPresolveReform + presolve_reform_repr::DwPresolveReform, + restr_partial_sol ) presolve_master_repr = presolve_reform_repr.original_master partial_solution = zeros(Float64, presolve_master_repr.form.nb_vars) @@ -196,7 +171,7 @@ function partial_sol_on_repr( dw_pricing_sps = dw_pricing_sps nb_fixed_columns = Dict(spid => 0 for (spid, _) in dw_pricing_sps) new_column_explored = false - for (col, partial_sol_value) in enumerate(presolve_master_restr.form.unpropagated_partial_solution) + for (col, partial_sol_value) in enumerate(restr_partial_sol) if abs(partial_sol_value) > Coluna.TOL var = presolve_master_restr.col_to_var[col] varid = getid(var) @@ -221,14 +196,7 @@ function partial_sol_on_repr( return partial_solution, nb_fixed_columns end -function propagate_partial_sol_to_repr_master!( - dw_pricing_sps::Dict, - presolve_reform::DwPresolveReform -) - partial_solution, nb_fixed_columns_per_sp = partial_sol_on_repr(dw_pricing_sps, presolve_reform) - - presolve_repr_master = presolve_reform.original_master - +function propagate_partial_sol_to_global_bounds!(presolve_repr_master, local_repr_partial_sol) new_lbs = zeros(Float64, presolve_repr_master.form.nb_vars) new_ubs = zeros(Float64, presolve_repr_master.form.nb_vars) @@ -241,7 +209,7 @@ function propagate_partial_sol_to_repr_master!( # -5 <= x <= 0 & x = -2 -> -5 <= x <= -2 for (col, (val, lb, ub)) in enumerate( Iterators.zip( - partial_solution, + local_repr_partial_sol, presolve_repr_master.form.lbs, presolve_repr_master.form.ubs ) @@ -265,16 +233,4 @@ function propagate_partial_sol_to_repr_master!( presolve_repr_master.form.lbs = new_lbs presolve_repr_master.form.ubs = new_ubs - for (spid, presolve_sp) in presolve_reform.dw_sps - lm = presolve_sp.form.lower_multiplicity - um = presolve_sp.form.upper_multiplicity - - presolve_sp.form.lower_multiplicity = max(0, lm - nb_fixed_columns_per_sp[spid]) - presolve_sp.form.upper_multiplicity = max(0, um - nb_fixed_columns_per_sp[spid]) # TODO if < 0 -> error - end - - presolve_restr_master = presolve_reform.restricted_master - 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 \ No newline at end of file diff --git a/test/unit/Presolve/propagation.jl b/test/unit/Presolve/propagation.jl index ed0b20ebd..c01d4d5dd 100644 --- a/test/unit/Presolve/propagation.jl +++ b/test/unit/Presolve/propagation.jl @@ -478,25 +478,37 @@ function test_col_bounds_propagation_from_restricted_master() @test presolve_reform.dw_sps[2].form.lbs == [1, 1] @test presolve_reform.dw_sps[2].form.ubs == [2, 3] - Coluna.Algorithm.propagate_partial_sol_into_master( - presolve_reform, + local_restr_partial_sol = Coluna.Algorithm.propagate_partial_sol_into_master( + presolve_reform, + master_form, dw_pricing_sps ) + Coluna.Algorithm.update_partial_sol!( + master_form, + presolve_reform.restricted_master, + local_restr_partial_sol + ) + Coluna.Algorithm.update_reform_from_presolve!(master_form, dw_pricing_sps, presolve_reform) # repr before fixing # 1 <= x1 <= 4 (repr) # 1 <= x2 <= 6 (repr) + # L=1 U=2 + # 1 <= x1 <= 2 (sp), + # 1 <= x2 <= 3 (sp) # fixing MC3 [x1 = 2, x2 = 2] - # 2 <= x1 <= 4 => 0 <= x1 <= 2 - # 2 <= x2 <= 6 => 0 <= x2 <= 4 + # max(1-2, 0*1) <= x1 <= min(4-2, 1*2) => 0 <= x1 <= 2 + # max(1-2, 0*1) <= x2 <= min(6-2, 1*3) => 0 <= x2 <= 3 @test presolve_reform.original_master.form.lbs == [0, 0] - @test presolve_reform.original_master.form.ubs == [2, 4] + @test presolve_reform.original_master.form.ubs == [2, 3] @test presolve_reform.dw_sps[2].form.lbs == [1, 1] @test presolve_reform.dw_sps[2].form.ubs == [2, 3] + # @test local_restr_partial_sol[] + Coluna.Algorithm.presolve_iteration!(presolve_reform, master_form, dw_pricing_sps) @@ -652,40 +664,56 @@ function test_col_bounds_propagation_from_restricted_master2() ) ) - Coluna.Algorithm.propagate_partial_sol_into_master( - presolve_reform, + local_restr_partial_sol = Coluna.Algorithm.propagate_partial_sol_into_master( + presolve_reform, + master_form, dw_pricing_sps ) - @test presolve_reform.original_master.form.lbs == [-8.0, -28.0] - @test presolve_reform.original_master.form.ubs == [0.0, 0.0] + # Original Master + # min x1 + x2 + # s.t. x1 + x2 >= 10 + # -9 <= x1 <= 9 (repr) + # -30 <= x2 <= 0 (repr) + # + # lm = 0, um = 3 + # + # subproblem variables: + # -3 <= x1 <= 3, + # -10 <= x2 <= -1 + + # Fix MC2 [x1 = -1, x2 = -2] + # we should following global bounds + # max(-8, 2*-3) <= x1 <= min(11, 3*2) => -6 <= x1 <= 6 + # max(-28, 2*-10) <= x2 <= min(2, 3*0) => -20 <= x2 <= 0 + + @test presolve_reform.original_master.form.lbs == [-6.0, -20.0] + @test presolve_reform.original_master.form.ubs == [6.0, 0.0] @test presolve_reform.dw_sps[2].form.lbs == [-3.0, -10.0] @test presolve_reform.dw_sps[2].form.ubs == [3.0, -1.0] Coluna.Algorithm.presolve_iteration!(presolve_reform, master_form, dw_pricing_sps) + Coluna.Algorithm.update_partial_sol!( + master_form, + presolve_reform.restricted_master, + local_restr_partial_sol + ) Coluna.Algorithm.update_reform_from_presolve!(master_form, dw_pricing_sps, presolve_reform) - @test presolve_reform.original_master.form.lbs == [-6.0, -20.0] - @test presolve_reform.original_master.form.ubs == [0.0, 0.0] + # Global bounds: + # -6 <= x1 <= 6 + # -20 <= x2 <= 0 (new local bounds => -20 <= x2 <= -2 because um = 2 and global ub = -1) - @test presolve_reform.dw_sps[2].form.lbs == [-3.0, -10.0] - @test presolve_reform.dw_sps[2].form.ubs == [0.0, -1.0] + # new local bounds: (lm = 0, um = 2) + # -3 <= x1 <= 3 + # -10 <= x2 <= -1 - # Original Master - # min x1 + x2 - # s.t. x1 + x2 >= 10 - # -9 <= x1 <= 9 (repr) - # -30 <= x2 <= 0 (repr) - - # Fix MC2 [x1 = -1, x2 = -2] - # we should following global bounds - # -9 <= x1 <= 0, => -8 <= x1 <= 0 - # -30 <= x2 <= 0 => -28 <= x2 <= 0 + @test presolve_reform.original_master.form.lbs == [-6.0, -20.0] + @test presolve_reform.original_master.form.ubs == [6.0, -0.0] - # and the following local bounds - # -3 <= x1 <= 0 => global lb bound = -9 - # -10 <= x2 <= -1 => global lb bound = -20 + @test presolve_reform.dw_sps[2].form.lbs == [-3.0, -10.0] + @test presolve_reform.dw_sps[2].form.ubs == [3.0, -1.0] # Partial solution: MC3 = 1 @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC1"]) ≈ 0 @@ -707,13 +735,13 @@ function test_col_bounds_propagation_from_restricted_master2() # @test Coluna.MathProg.get_value_in_partial_sol(spform, sp_name_to_var["x2"]) ≈ 2 @test Coluna.MathProg.getcurlb(spform, sp_name_to_var["x1"]) ≈ -3 - @test Coluna.MathProg.getcurub(spform, sp_name_to_var["x1"]) ≈ 0 + @test Coluna.MathProg.getcurub(spform, sp_name_to_var["x1"]) ≈ 3 @test Coluna.MathProg.getcurlb(spform, sp_name_to_var["x2"]) ≈ -10 @test Coluna.MathProg.getcurub(spform, sp_name_to_var["x2"]) ≈ -1 # Check global bounds (repr bounds) @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["x1"]) ≈ -6 - @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x1"]) ≈ 0 + @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x1"]) ≈ 6 @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["x2"]) ≈ -20 @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x2"]) ≈ 0 return From 1cfd4c347eafe70a64efc899f819db08e9b95713 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Mon, 23 Oct 2023 17:21:19 +0200 Subject: [PATCH 2/7] making progress in refactoring --- src/Algorithm/presolve/helpers.jl | 211 ++++++-------- src/Algorithm/presolve/interface.jl | 128 +++++---- src/Algorithm/presolve/propagation.jl | 6 +- test/unit/Presolve/build_formulation.jl | 12 +- test/unit/Presolve/columns.jl | 2 +- test/unit/Presolve/helpers.jl | 56 ++-- test/unit/Presolve/partial_solution.jl | 8 +- test/unit/Presolve/propagation.jl | 360 +++++++----------------- 8 files changed, 301 insertions(+), 482 deletions(-) diff --git a/src/Algorithm/presolve/helpers.jl b/src/Algorithm/presolve/helpers.jl index 78e3f2385..f023133be 100644 --- a/src/Algorithm/presolve/helpers.jl +++ b/src/Algorithm/presolve/helpers.jl @@ -13,15 +13,12 @@ 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; - unpropagated_partial_solution = nothing + 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.")) @@ -30,14 +27,6 @@ function PresolveFormRepr( @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, @@ -48,8 +37,6 @@ function PresolveFormRepr( lbs, ubs, partial_solution, - unpropagated_partial_solution_vec, - unpropagated_partial_solution_flag, lm, um ) @@ -248,91 +235,87 @@ function tighten_bounds_presolve_form_repr(form::PresolveFormRepr, tightened_bou col_mask end -function partial_sol_update(form::PresolveFormRepr, lm, um, store_unpropagated_partial_sol) - 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)) - 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 - - # Update rhs - new_rhs = rhs - coef_matrix * new_partial_sol - - # Update bounds - new_lbs = lbs - new_partial_sol - new_ubs = ubs - new_partial_sol - - # Update partial_sol - 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; - unpropagated_partial_solution = store_unpropagated_partial_sol ? new_partial_sol : nothing - ), - 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 - - 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}[] - 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 partial_sol_update(form::PresolveFormRepr, lm, um, store_unpropagated_partial_sol) +# 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)) +# 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 + +# # Update rhs +# new_rhs = rhs - coef_matrix * new_partial_sol + +# # Update bounds +# new_lbs = lbs - new_partial_sol +# new_ubs = ubs - new_partial_sol + +# # Update partial_sol +# 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; +# unpropagated_partial_solution = store_unpropagated_partial_sol ? new_partial_sol : nothing +# ), +# 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 + + +# # 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, @@ -340,27 +323,19 @@ function PresolveFormRepr( tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}}, lm, um; - tighten_bounds = true, - partial_sol = true, - shrink = true, - store_unpropagated_partial_sol = 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, store_unpropagated_partial_sol) - 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 + + presolve_form_repr, row_mask, col_mask = tighten_bounds_presolve_form_repr( + presolve_form_repr, tightened_bounds, lm, um + ) + + # 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 end diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index 1a9623c39..0f522a9cf 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -1,3 +1,8 @@ +""" +Stores a matrix-representation of the formulation and the mapping between the variables & +constraints of the formulation to the row and column of the matrix and components of the +vector that represents the formulation. +""" struct PresolveFormulation col_to_var::Vector{Variable} row_to_constr::Vector{Constraint} @@ -8,8 +13,18 @@ struct PresolveFormulation fixed_variables::Dict{VarId, Float64} end +""" +Stores the presolve-representation of the formulations of the Dantzig-Wolfe reformulation. +This datastructure contains: + +- `representative_master` that contains the master formulation expressed with representative + variables and pure master variables +- `restricted_master` that contains the master formulation expressed with pure master variables, + master columns, and artificial variables +- `dw_sps` a dictionary that contains the subproblem formulations. +""" mutable struct DwPresolveReform - original_master::PresolveFormulation + representative_master::PresolveFormulation restricted_master::PresolveFormulation dw_sps::Dict{FormId, PresolveFormulation} end @@ -109,22 +124,14 @@ end function propagate_in_presolve_form( form::PresolveFormulation, rows_to_deactivate::Vector{Int}, - tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}}; - tighten_bounds = true, - partial_sol = true, - shrink = true, - store_unpropagated_partial_sol = true + tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}} ) - form_repr, row_mask, col_mask, fixed_vars = PresolveFormRepr( + form_repr, row_mask, col_mask = 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, - store_unpropagated_partial_sol = store_unpropagated_partial_sol + form.form.upper_multiplicity ) col_to_var = form.col_to_var[col_mask] @@ -136,23 +143,21 @@ function propagate_in_presolve_form( 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)) - if shrink - for constr in form.row_to_constr[.!row_mask] - push!(deactivated_constrs, getid(constr)) - end - - 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 + for constr in form.row_to_constr[.!row_mask] + push!(deactivated_constrs, getid(constr)) end + # 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 + @assert length(col_to_var) == length(form_repr.lbs) @assert length(col_to_var) == length(form_repr.ubs) @assert length(row_to_constr) == length(form_repr.rhs) @@ -255,12 +260,14 @@ function update_partial_sol!(form::Formulation{DwMaster}, presolve_form::Presolv partial_sol_counter = 0 for (col, val) in enumerate(partial_solution) var = presolve_form.col_to_var[col] - if getduty(getid(var)) <= MasterArtVar && !iszero(val) + duty = getduty(getid(var)) + if duty <= MasterArtVar && !iszero(val) error(""" Infeasible because artificial variable $(getname(form, var)) is not zero. Fixed to $(val) in partial solution. """) end - if !iszero(val) + 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) @@ -317,17 +324,17 @@ function update_form_from_presolve!(form::Formulation, presolve_form::PresolveFo end end - # Fixed variables - var_fix_counter = 0 - for (var_id, val) in presolve_form.fixed_variables - if iscuractive(form, var_id) - 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) - var_fix_counter += 1 - end - end + # # Fixed variables + # var_fix_counter = 0 + # for (var_id, val) in presolve_form.fixed_variables + # if iscuractive(form, var_id) + # 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) + # var_fix_counter += 1 + # end + # end _update_rhs!(form, presolve_form) _update_bounds!(form, presolve_form) @@ -345,7 +352,7 @@ function update_reform_from_presolve!( update_form_from_presolve!(sp, sp_presolve_form) end - presolve_repr_master = presolve_reform.original_master + presolve_repr_master = presolve_reform.representative_master update_form_from_presolve!(master, presolve_repr_master) return end @@ -472,6 +479,11 @@ function compute_default_global_bounds(presolve_repr_master, presolve_dw_pricing return master_repr_var_bounds end +""" + + +Returns the local restricted partial solution. +""" function propagate_partial_sol_into_master(presolve_reform, master, dw_pricing_sps) presolve_restricted_master = presolve_reform.restricted_master @@ -481,7 +493,7 @@ function propagate_partial_sol_into_master(presolve_reform, master, dw_pricing_s # Step 2: compute the rhs of all constraints. # Non-robust and convexity constraints rhs can only be computed using this representation. - rhs = compute_rhs(presolve_restricted_master, local_restr_partial_sol) + new_rhs = compute_rhs(presolve_restricted_master, local_restr_partial_sol) # Step 3: project local partial solution on the representative master. local_repr_partial_sol, nb_fixed_columns_per_sp = partial_sol_on_repr( @@ -493,47 +505,50 @@ function propagate_partial_sol_into_master(presolve_reform, master, dw_pricing_s # Step 5: compute new default global bounds master_repr_default_global_bounds = compute_default_global_bounds( - presolve_reform.original_master, presolve_reform.dw_sps, master, dw_pricing_sps + presolve_reform.representative_master, presolve_reform.dw_sps, master, dw_pricing_sps ) # Step 6: propagate local partial solution from the representative master representation # into the global bounds. propagate_partial_sol_to_global_bounds!( - presolve_reform.original_master, + presolve_reform.representative_master, local_repr_partial_sol, master_repr_default_global_bounds ) # Step 7: Update the rhs of the representative master. - @assert length(presolve_reform.restricted_master.form.rhs) == length(presolve_reform.original_master.form.rhs) - for (row, rhs) in enumerate(presolve_reform.restricted_master.form.rhs) - presolve_reform.original_master.form.rhs[row] = rhs + @assert length(new_rhs) == length(presolve_reform.restricted_master.form.rhs) == length(presolve_reform.representative_master.form.rhs) + for (row, rhs) in enumerate(new_rhs) + presolve_reform.representative_master.form.rhs[row] = rhs end return local_restr_partial_sol end function presolve_iteration!(presolve_reform, master, dw_pricing_sps) - # Step 5: Propagate and strengthen local and global bounds. + # Propagate and strengthen local and global bounds. + # At the moment, we perform two rounds of local/global bounds strenthening and propagation. propagate_global_to_local_bounds!(master, dw_pricing_sps, presolve_reform) propagate_local_to_global_bounds!(master, dw_pricing_sps, presolve_reform) propagate_global_to_local_bounds!(master, dw_pricing_sps, presolve_reform) propagate_local_to_global_bounds!(master, dw_pricing_sps, presolve_reform) - # Step 3: presolve the respresentative master. - # Bounds tightening, we do not shrink the formulation. - tightened_bounds_repr = bounds_tightening(presolve_reform.original_master.form) + # Presolve the respresentative master. + # Bounds tightening, we do not change + tightened_bounds_repr = bounds_tightening(presolve_reform.representative_master.form) new_repr_master = propagate_in_presolve_form( - presolve_reform.original_master, - Int[], - tightened_bounds_repr; - store_unpropagated_partial_sol = false + presolve_reform.representative_master, + Int[], + tightened_bounds_repr ) - presolve_reform.original_master = new_repr_master + presolve_reform.representative_master = new_repr_master return end function _presolve_run!(presolve_reform, master, dw_pricing_sps) + # Step 1: identify the partial solution in the restricted master, compute the new rhs + # of all master constraints and new global and local bounds of the representative and + # subproblem variables. local_restr_partial_sol = propagate_partial_sol_into_master( presolve_reform, master, @@ -595,4 +610,3 @@ function _column_is_proper(col_id, sp_form) end return true end - diff --git a/src/Algorithm/presolve/propagation.jl b/src/Algorithm/presolve/propagation.jl index adf5af4b7..0b72626b8 100644 --- a/src/Algorithm/presolve/propagation.jl +++ b/src/Algorithm/presolve/propagation.jl @@ -61,7 +61,7 @@ function propagate_local_to_global_bounds!( dw_pricing_sps::Dict, presolve_reform_repr::DwPresolveReform ) - presolve_repr_master = presolve_reform_repr.original_master + presolve_repr_master = presolve_reform_repr.representative_master for (spid, spform) in dw_pricing_sps presolve_sp = presolve_reform_repr.dw_sps[spid] propagate_global_bounds!(presolve_repr_master, master, presolve_sp, spform) @@ -94,7 +94,7 @@ function propagate_global_to_local_bounds!( dw_pricing_sps::Dict, presolve_reform_repr::DwPresolveReform ) - presolve_repr_master = presolve_reform_repr.original_master + presolve_repr_master = presolve_reform_repr.representative_master for (spid, spform) in dw_pricing_sps presolve_sp = presolve_reform_repr.dw_sps[spid] propagate_local_bounds!(presolve_repr_master, master, presolve_sp, spform) @@ -162,7 +162,7 @@ function partial_sol_on_repr( presolve_reform_repr::DwPresolveReform, restr_partial_sol ) - presolve_master_repr = presolve_reform_repr.original_master + presolve_master_repr = presolve_reform_repr.representative_master partial_solution = zeros(Float64, presolve_master_repr.form.nb_vars) # partial solution diff --git a/test/unit/Presolve/build_formulation.jl b/test/unit/Presolve/build_formulation.jl index 805901ed4..7993e93e5 100644 --- a/test/unit/Presolve/build_formulation.jl +++ b/test/unit/Presolve/build_formulation.jl @@ -103,7 +103,7 @@ function build_dw_presolve_reformulation() reform, master, sps = presolve_toy_gap_with_penalties() presolve_reform = Coluna.Algorithm.create_presolve_reform(reform) - presolve_original_master = presolve_reform.original_master + presolve_original_master = presolve_reform.representative_master mast_var_ids = Dict{String, Int}(ClMP.getname(master, var) => k for (k, var) in enumerate(presolve_original_master.col_to_var)) var_ids_lbs_ubs = [ @@ -344,7 +344,7 @@ function build_dw_presolve_reformulation() @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_16"]] == 1.0 @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_17"]] == 1.0 end -register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation) +register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation; f = true) function presolve_toy_gap_with_penalties2() form = """ @@ -451,7 +451,7 @@ function build_dw_presolve_reformulation2() reform, master, sps = presolve_toy_gap_with_penalties2() presolve_reform = Coluna.Algorithm.create_presolve_reform(reform) - presolve_original_master = presolve_reform.original_master + presolve_original_master = presolve_reform.representative_master mast_var_ids = Dict{String, Int}(ClMP.getname(master, var) => k for (k, var) in enumerate(presolve_original_master.col_to_var)) var_ids_lbs_ubs = [ @@ -692,7 +692,7 @@ function build_dw_presolve_reformulation2() @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_16"]] == 1.0 @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_17"]] == 1.0 end -register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation2) +register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation2; f = true) function presolve_reformulation_with_var_not_in_coeff_matrix() form = """ @@ -770,7 +770,7 @@ function build_dw_presolve_reformulation_with_var_not_in_coeff_matrix() reform, master, sps = presolve_reformulation_with_var_not_in_coeff_matrix() presolve_reform = Coluna.Algorithm.create_presolve_reform(reform) - presolve_original_master = presolve_reform.original_master + presolve_original_master = presolve_reform.representative_master mast_var_ids = Dict{String, Int}(ClMP.getname(master, var) => k for (k, var) in enumerate(presolve_original_master.col_to_var)) var_ids_lbs_ubs = [ @@ -825,4 +825,4 @@ function build_dw_presolve_reformulation_with_var_not_in_coeff_matrix() @test presolve_original_master.form.col_major_coef_matrix[c, v] == val end end -register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation_with_var_not_in_coeff_matrix) +register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation_with_var_not_in_coeff_matrix; f = true) diff --git a/test/unit/Presolve/columns.jl b/test/unit/Presolve/columns.jl index bd845eba1..6409f6dba 100644 --- a/test/unit/Presolve/columns.jl +++ b/test/unit/Presolve/columns.jl @@ -89,4 +89,4 @@ function test_non_proper_column1() return end -register!(unit_tests, "columns", test_non_proper_column1) \ No newline at end of file +register!(unit_tests, "columns", test_non_proper_column1; f = true) \ No newline at end of file diff --git a/test/unit/Presolve/helpers.jl b/test/unit/Presolve/helpers.jl index 0e12c6e5a..7e479c084 100644 --- a/test/unit/Presolve/helpers.jl +++ b/test/unit/Presolve/helpers.jl @@ -15,7 +15,7 @@ function test_lb_precision() @test Coluna.Algorithm._lb_prec(e) == 0.3 @test Coluna.Algorithm._lb_prec(f) == 0.3 end -register!(unit_tests, "presolve_helper", test_lb_precision) +register!(unit_tests, "presolve_helper", test_lb_precision; f = true) function test_ub_precision() z = 1.20000000000000001 @@ -34,7 +34,7 @@ function test_ub_precision() @test Coluna.Algorithm._ub_prec(e) == 0.3 @test Coluna.Algorithm._ub_prec(f) == 0.3 end -register!(unit_tests, "presolve_helper", test_ub_precision) +register!(unit_tests, "presolve_helper", test_ub_precision; f = true) function test_presolve_builder1() coef_matrix = sparse([ @@ -62,7 +62,7 @@ function test_presolve_builder1() @test all(form.ubs .== ubs) return end -register!(unit_tests, "presolve_helper", test_presolve_builder1) +register!(unit_tests, "presolve_helper", test_presolve_builder1; f = true) # Test rows deactivation. function test_presolve_builder2() @@ -100,7 +100,7 @@ function test_presolve_builder2() @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) +register!(unit_tests, "presolve_helper", test_presolve_builder2; f = true) # Test vars fixing. function test_presolve_builder3() @@ -155,7 +155,7 @@ function test_presolve_builder3() @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) +register!(unit_tests, "presolve_helper", test_presolve_builder3; f = true) # Test bound tightening. function test_presolve_builder4() @@ -196,7 +196,7 @@ function test_presolve_builder4() @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) +register!(unit_tests, "presolve_helper", test_presolve_builder4; f = true) function test_presolve_builder5() # 2x1 + 3x2 - 2x3 >= 2 @@ -233,7 +233,7 @@ function test_presolve_builder5() @test all(form2.ubs .== [Inf, Inf, 1]) @test all(form2.partial_solution .== [2, 2, 1]) end -register!(unit_tests, "presolve_helper", test_presolve_builder5) +register!(unit_tests, "presolve_helper", test_presolve_builder5; f = true) function row_activity() coef_matrix = sparse([ @@ -266,7 +266,7 @@ function row_activity() @test Coluna.Algorithm.row_min_activity(form, 6) == transpose(coef_matrix[6,:]) * [lbs[1], ubs[2], lbs[3], lbs[4], 0, lbs[6], lbs[7]] # ok @test Coluna.Algorithm.row_max_activity(form, 6) == transpose(coef_matrix[6,:]) * [ubs[1], lbs[2], ubs[3], ubs[4], 0, ubs[6], ubs[7]] # ok end -register!(unit_tests, "presolve_helper", row_activity) +register!(unit_tests, "presolve_helper", row_activity; f = true) function row_slack() coef_matrix = sparse([ @@ -299,7 +299,7 @@ function row_slack() @test Coluna.Algorithm.row_min_slack(form, 6) == rhs[6] - Coluna.Algorithm.row_max_activity(form, 6) # ok @test Coluna.Algorithm.row_max_slack(form, 6) == rhs[6] - Coluna.Algorithm.row_min_activity(form, 6) # ok end -register!(unit_tests, "presolve_helper", row_slack) +register!(unit_tests, "presolve_helper", row_slack; f = true) function test_inner_unbounded_row() @test Coluna.Algorithm._unbounded_row(Less, Inf) @@ -311,7 +311,7 @@ function test_inner_unbounded_row() @test !Coluna.Algorithm._unbounded_row(Less, 15) @test !Coluna.Algorithm._unbounded_row(Greater, 15) end -register!(unit_tests, "presolve_helper", test_inner_unbounded_row) +register!(unit_tests, "presolve_helper", test_inner_unbounded_row; f = true) function test_inner_row_bounded_by_var_bounds_1() # x + y + z >= 1 @@ -343,7 +343,7 @@ function test_inner_row_bounded_by_var_bounds_1() @test !Coluna.Algorithm._row_bounded_by_var_bounds(sense, min_slack, max_slack, 1e-6) @test Coluna.Algorithm._infeasible_row(sense, min_slack, max_slack, 1e-6) end -register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_1) +register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_1; f = true) function test_inner_row_bounded_by_var_bounds_2() # x + y + z <= 9 @@ -375,7 +375,7 @@ function test_inner_row_bounded_by_var_bounds_2() @test !Coluna.Algorithm._row_bounded_by_var_bounds(sense, min_slack, max_slack, 1e-6) @test Coluna.Algorithm._infeasible_row(sense, min_slack, max_slack, 1e-6) end -register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_2) +register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_2; f = true) function test_inner_row_bounded_by_var_bounds_3() # x + y + z == 3 @@ -410,7 +410,7 @@ function test_inner_row_bounded_by_var_bounds_3() @test !Coluna.Algorithm._row_bounded_by_var_bounds(sense, min_slack, max_slack, 1e-6) @test !Coluna.Algorithm._infeasible_row(sense, min_slack, max_slack, 1e-6) end -register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_3) +register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_3; f = true) function test_var_bounds_from_row1() # x + 2y + 3z >= 10 @@ -448,7 +448,7 @@ function test_var_bounds_from_row1() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, -1.0) @test isinf(ub) && ub > 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row1) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row1; f = true) function test_var_bounds_from_row2() # -3x + y + 2z <= 2 @@ -487,7 +487,7 @@ function test_var_bounds_from_row2() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, 3) @test isinf(ub) && ub > 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row2) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row2; f = true) function test_var_bounds_from_row3() # 2x + 3y - 4z <= 9 @@ -526,7 +526,7 @@ function test_var_bounds_from_row3() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, -2) @test ub == 1/2 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row3) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row3; f = true) function test_var_bounds_from_row4() # -2x + 2y + 3z >= 10 @@ -565,7 +565,7 @@ function test_var_bounds_from_row4() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, 2) @test ub == 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row4) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row4; f = true) function test_var_bounds_from_row5() # 2x + 3y + 4z = 5 @@ -601,7 +601,7 @@ function test_var_bounds_from_row5() ub = Coluna.Algorithm._var_ub_from_row(sense[1], min_slack, max_slack, 2) @test ub == 5/2 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row5) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row5; f = true) function test_var_bounds_from_row6() # x1 + x2 >= 1 (row 1) @@ -641,7 +641,7 @@ function test_var_bounds_from_row6() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack2, max_slack2, 1) @test ub == Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row6) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row6; f = true) function test_var_bounds_from_row7() # -2x + y + z >= 150 @@ -687,7 +687,7 @@ function test_var_bounds_from_row7() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, -1) @test isinf(ub) && ub > 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row7) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row7; f = true) function test_var_bounds_from_row8() # this was producing a bug # 2x + y + z <= 1 @@ -715,7 +715,7 @@ function test_var_bounds_from_row8() # this was producing a bug ub = Coluna.Algorithm._var_ub_from_row(sense[1], min_slack, max_slack, 2) @test ub == 1/2 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row8) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row8; f = true) function test_var_bounds_from_row9() # x + y + a >= 1 @@ -753,7 +753,7 @@ function test_var_bounds_from_row9() @test result[1] === (0.0, false, 0.0, true) @test result[2] === (1.0, false, 1.0, true) end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row9) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row9; f = true) function test_var_bounds_from_row10() # y2 @@ -782,7 +782,7 @@ function test_var_bounds_from_row10() lb = Coluna.Algorithm._var_lb_from_row(sense[1], min_slack, max_slack, 1) @test lb == -Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row10) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row10; f = true) function test_var_bounds_from_row11() # - w - x + y + z = 0 @@ -810,7 +810,7 @@ function test_var_bounds_from_row11() lb = Coluna.Algorithm._var_lb_from_row(sense[1], min_slack, max_slack, -1) @test lb == -Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row11) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row11; f = true) function test_var_bounds_from_row12() # 0x + y + z <= 5 @@ -836,7 +836,7 @@ function test_var_bounds_from_row12() lb = Coluna.Algorithm._var_lb_from_row(sense[1], min_slack, max_slack, 0) @test lb == -Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row12) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row12; f = true) function test_uninvolved_vars1() # 0x + y + z <= 5 @@ -857,7 +857,7 @@ function test_uninvolved_vars1() @test cols == [1] end -register!(unit_tests, "presolve_helper", test_uninvolved_vars1) +register!(unit_tests, "presolve_helper", test_uninvolved_vars1; f = true) function test_uninvolved_vars2() # x + y + z <= 5 @@ -878,7 +878,7 @@ function test_uninvolved_vars2() @test cols == [] end -register!(unit_tests, "presolve_helper", test_uninvolved_vars2) +register!(unit_tests, "presolve_helper", test_uninvolved_vars2; f = true) function test_uninvolved_vars3() # w, x, y, z @@ -898,4 +898,4 @@ function test_uninvolved_vars3() cols = Coluna.Algorithm.find_uninvolved_vars(form.col_major_coef_matrix) @test cols == [1, 4] end -register!(unit_tests, "presolve_helper", test_uninvolved_vars3) \ No newline at end of file +register!(unit_tests, "presolve_helper", test_uninvolved_vars3; f = true) \ No newline at end of file diff --git a/test/unit/Presolve/partial_solution.jl b/test/unit/Presolve/partial_solution.jl index 8bd2a3c80..8db2a8cdd 100644 --- a/test/unit/Presolve/partial_solution.jl +++ b/test/unit/Presolve/partial_solution.jl @@ -40,7 +40,7 @@ function test_partial_solution1() @test form2.ubs == [4.0, 4.0] @test form2.partial_solution == [2.0, 4.0] end -register!(unit_tests, "presolve_partial_sol", test_partial_solution1) +register!(unit_tests, "presolve_partial_sol", test_partial_solution1; f = true) function test_partial_solution2() # 2x + 3y <= 5 @@ -84,7 +84,7 @@ function test_partial_solution2() @test all(form2.ubs .== [Inf, Inf]) @test all(form2.partial_solution .== [3.0, 3.0]) end -register!(unit_tests, "presolve_partial_sol", test_partial_solution2) +register!(unit_tests, "presolve_partial_sol", test_partial_solution2; f = true) function test_partial_solution3() # 2x + 3y <= 5 @@ -127,7 +127,7 @@ function test_partial_solution3() @test all(form2.ubs .== [-0.0, 8.0]) @test all(form2.partial_solution .== [-3.0, 0.0]) end -register!(unit_tests, "presolve_partial_sol", test_partial_solution3) +register!(unit_tests, "presolve_partial_sol", test_partial_solution3; f = true) function test_partial_solution4() # 2x + 3y <= 5 @@ -170,4 +170,4 @@ function test_partial_solution4() @test all(form2.ubs .== [0.0, 0.0]) @test all(form2.partial_solution .== [-1.0, -1.0]) end -register!(unit_tests, "presolve_partial_sol", test_partial_solution4) \ No newline at end of file +register!(unit_tests, "presolve_partial_sol", test_partial_solution4; f = true) \ No newline at end of file diff --git a/test/unit/Presolve/propagation.jl b/test/unit/Presolve/propagation.jl index c01d4d5dd..7fabd7872 100644 --- a/test/unit/Presolve/propagation.jl +++ b/test/unit/Presolve/propagation.jl @@ -115,180 +115,6 @@ function _presolve_formulation(var_names, constr_names, matrix, form, name_to_va return presolve_form end -############################################################################################ -# Constraint removing propagation. -############################################################################################ - -## OriginalConstr -> MasterMixedConstr -## OriginalConstr -> MasterPureConstr -function test_constr_removing_propagation_from_original_to_master() - # Original - # max x + y - # s.t. x + y <= 1 - # x + y <= 3 (remove) - # 0 <= x <= 1 - # 0 <= y <= 1 - - # Master - # max _x + _y + MC1 + 1000a - # s.t. _x + _y + MC1 + a <= 1 - # _x + _y + MC1 + a <= 3 (remove by propagation) - # 0 <= _x <= 1 (repr) - # 0 <= _y <= 1 (repr) - # 0 <= MC1 <= 1 - # a >= 0 - - env = Coluna.Env{Coluna.MathProg.VarId}(Coluna.Params()) - - orig_form, orig_name_to_var, orig_name_to_constr = _mathprog_formulation!( - env, - Coluna.MathProg.Original(), - [ - # name, duty, cost, lb, ub, id - ("x", Coluna.MathProg.OriginalVar, 1.0, 0.0, 1.0, nothing, nothing), - ("y", Coluna.MathProg.OriginalVar, 1.0, 0.0, 1.0, nothing, nothing) - ], - [ - # name, duty, rhs, sense, id - ("c1", Coluna.MathProg.OriginalConstr, 1.0, ClMP.Less, nothing, nothing), - ("c2", Coluna.MathProg.OriginalConstr, 3.0, ClMP.Less, nothing, nothing) - ] - ) - - orig_presolve_form = _presolve_formulation( - ["x", "y"], ["c1", "c2"], [1 1; 1 1], orig_form, orig_name_to_var, orig_name_to_constr - ) - - master_form, master_name_to_var, master_constr_to_var = _mathprog_formulation!( - env, - Coluna.MathProg.DwMaster(), - [ - # name, duty, cost, lb, ub, id - ("_x", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(orig_name_to_var["x"]), nothing), - ("_y", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(orig_name_to_var["y"]), nothing), - ("MC1", Coluna.MathProg.MasterCol, 1.0, 0.0, 1.0, nothing, nothing), - ("a", Coluna.MathProg.MasterArtVar, 1000.0, 0.0, Inf, nothing, nothing) - ], - [ - # name, duty, rhs, lb, ub, id - ("c1", Coluna.MathProg.MasterPureConstr, 1.0, ClMP.Less, Coluna.Algorithm.getid(orig_name_to_constr["c1"]), nothing), - ("c2", Coluna.MathProg.MasterPureConstr, 3.0, ClMP.Less, Coluna.Algorithm.getid(orig_name_to_constr["c2"]), nothing) - ] - ) - - master_repr_presolve_form = _presolve_formulation( - ["_x", "_y"], ["c1", "c2"], [1 1; 1 1], master_form, master_name_to_var, master_constr_to_var - ) - - # Run the presolve row deactivation on the original formulation. - result = Coluna.Algorithm.rows_to_deactivate(orig_presolve_form.form) - - # Test if the constraint was deactivated. - @test result == [2] # remove row 2 of original formulation - - # Propagate - - - # Test propagation -end -register!(unit_tests, "presolve_propagation", test_constr_removing_propagation_from_original_to_master) - -## OriginalConstr -> DwSpPureConstr -function test_constr_removing_propagation_from_original_to_subproblem() - # Original - # max x1 + x2 + y1 + y2 - # s.t. x1 + x2 + y1 + y2 <= 2 - # x1 + x2 <= 2 (remove) - # y1 + y2 <= 1 - # 0 <= x1, x2 <= 1 - # 0 <= y1, y2 <= 2 - - # Subproblems - # max x1 + x2 - # s.t. x1 + x2 <= 2 (remove by propagation) - # 0 <= x1, x2 <= 1 - - # max y1 + y2 - # s.t. y1 + y2 <= 1 - # 0 <= y1, y2 <= 1 - - env = Coluna.Env{Coluna.MathProg.VarId}(Coluna.Params()) - - orig_form, orig_name_to_var, orig_name_to_constr = _mathprog_formulation!( - env, - Coluna.MathProg.Original(), - [ - # name, duty, cost, lb, ub, id - ("x1", Coluna.MathProg.OriginalVar, 1.0, 0.0, 1.0, nothing, nothing), - ("x2", Coluna.MathProg.OriginalVar, 1.0, 0.0, 1.0, nothing, nothing), - ("y1", Coluna.MathProg.OriginalVar, 1.0, 0.0, 2.0, nothing, nothing), - ("y2", Coluna.MathProg.OriginalVar, 1.0, 0.0, 2.0, nothing, nothing) - ], - [ - # name, duty, rhs, sense, id - ("c1", Coluna.MathProg.OriginalConstr, 3.0, ClMP.Less, nothing, nothing), - ("c2", Coluna.MathProg.OriginalConstr, 2.0, ClMP.Less, nothing, nothing), - ("c3", Coluna.MathProg.OriginalConstr, 1.0, ClMP.Less, nothing, nothing) - ] - ) - - orig_presolve_form = _presolve_formulation( - ["x1", "x2", "y1", "y2"], - ["c1", "c2", "c3"], - [1 1 1 1; 1 1 0 0; 0 0 1 1], - orig_form, - orig_name_to_var, - orig_name_to_constr - ) - - sp1_form, sp1_name_to_var, sp1_name_to_constr = _mathprog_formulation!( - env, - Coluna.MathProg.DwSp( - nothing, nothing, nothing, ClMP.Continuous, Coluna.MathProg.Pool() - ), - [ - # name, duty, cost, lb, ub, id - ("x1", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(orig_name_to_var["x1"]), nothing), - ("x2", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(orig_name_to_var["x2"]), nothing) - ], - [ - # name, duty, rhs, sense, id - ("c2", Coluna.MathProg.DwSpPureConstr, 2.0, ClMP.Less, Coluna.Algorithm.getid(orig_name_to_constr["c2"]), nothing) - ], - ) - - sp1_presolve_form = _presolve_formulation( - ["x1", "x2"], ["c2"], [1 1;], sp1_form, sp1_name_to_var, sp1_name_to_constr - ) - - sp2_form, sp2_name_to_var, sp2_name_to_constr = _mathprog_formulation!( - env, - Coluna.MathProg.DwSp( - nothing, nothing, nothing, ClMP.Continuous, Coluna.MathProg.Pool() - ), - [ - # name, duty, cost, lb, ub, id - ("y1", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(orig_name_to_var["y1"]), nothing), - ("y2", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(orig_name_to_var["y2"]), nothing) - ], - [ - # name, duty, rhs, sense, id - ("c3", Coluna.MathProg.DwSpPureConstr, 1.0, ClMP.Less, Coluna.Algorithm.getid(orig_name_to_constr["c3"]), nothing) - ], - ) - - sp2_presolve_form = _presolve_formulation( - ["y1", "y2"], ["c3"], [1 1;], sp2_form, sp2_name_to_var, sp2_name_to_constr - ) - - # Run the presolve row deactivation on the original formulation. - result = Coluna.Algorithm.rows_to_deactivate(orig_presolve_form.form) - - # Test if the constraint was deactivated. - @test result == [2] # remove row 2 of original formulation -end -register!(unit_tests, "presolve_propagation", test_constr_removing_propagation_from_original_to_subproblem) - ############################################################################################ # Variable bound propagation. ############################################################################################ @@ -339,11 +165,12 @@ function test_var_bound_propagation_within_restricted_master() new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( master_presolve_form, Int[], - result; - store_unpropagated_partial_sol = false + result ) 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) + new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( + new_master_presolve_form, Int[], no_tightening + ) Coluna.Algorithm.update_form_from_presolve!(master_form, new_master_presolve_form) @@ -359,8 +186,12 @@ function test_var_bound_propagation_within_restricted_master() @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c1"]) == 0.0 @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c2"]) == 1.0 @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c3"]) == 0.0 + + @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC1"]) == 0.0 + @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC2"]) == 1.0 + @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC3"]) == 0.0 end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_within_restricted_master) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_within_restricted_master; f = true) function test_col_bounds_propagation_from_restricted_master() # Original Master @@ -472,8 +303,8 @@ function test_col_bounds_propagation_from_restricted_master() ) ) - @test presolve_reform.original_master.form.lbs == [1, 1] - @test presolve_reform.original_master.form.ubs == [4, 6] + @test presolve_reform.representative_master.form.lbs == [1, 1] + @test presolve_reform.representative_master.form.ubs == [4, 6] @test presolve_reform.dw_sps[2].form.lbs == [1, 1] @test presolve_reform.dw_sps[2].form.ubs == [2, 3] @@ -501,8 +332,8 @@ function test_col_bounds_propagation_from_restricted_master() # max(1-2, 0*1) <= x1 <= min(4-2, 1*2) => 0 <= x1 <= 2 # max(1-2, 0*1) <= x2 <= min(6-2, 1*3) => 0 <= x2 <= 3 - @test presolve_reform.original_master.form.lbs == [0, 0] - @test presolve_reform.original_master.form.ubs == [2, 3] + @test presolve_reform.representative_master.form.lbs == [0, 0] + @test presolve_reform.representative_master.form.ubs == [2, 3] @test presolve_reform.dw_sps[2].form.lbs == [1, 1] @test presolve_reform.dw_sps[2].form.ubs == [2, 3] @@ -520,8 +351,8 @@ function test_col_bounds_propagation_from_restricted_master() # 0 <= x1 <= 2 # 0 <= x2 <= 3 => strengthen global bound because um = 1 - @test presolve_reform.original_master.form.lbs == [0, 0] - @test presolve_reform.original_master.form.ubs == [2, 3] + @test presolve_reform.representative_master.form.lbs == [0, 0] + @test presolve_reform.representative_master.form.ubs == [2, 3] @test presolve_reform.dw_sps[2].form.lbs == [1, 1] @test presolve_reform.dw_sps[2].form.ubs == [2, 3] @@ -554,7 +385,7 @@ function test_col_bounds_propagation_from_restricted_master() @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x2"]) ≈ 3 return end -register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master) +register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master; f = true) function test_col_bounds_propagation_from_restricted_master2() # Original Master @@ -687,8 +518,8 @@ function test_col_bounds_propagation_from_restricted_master2() # max(-8, 2*-3) <= x1 <= min(11, 3*2) => -6 <= x1 <= 6 # max(-28, 2*-10) <= x2 <= min(2, 3*0) => -20 <= x2 <= 0 - @test presolve_reform.original_master.form.lbs == [-6.0, -20.0] - @test presolve_reform.original_master.form.ubs == [6.0, 0.0] + @test presolve_reform.representative_master.form.lbs == [-6.0, -20.0] + @test presolve_reform.representative_master.form.ubs == [6.0, 0.0] @test presolve_reform.dw_sps[2].form.lbs == [-3.0, -10.0] @test presolve_reform.dw_sps[2].form.ubs == [3.0, -1.0] @@ -709,8 +540,8 @@ function test_col_bounds_propagation_from_restricted_master2() # -3 <= x1 <= 3 # -10 <= x2 <= -1 - @test presolve_reform.original_master.form.lbs == [-6.0, -20.0] - @test presolve_reform.original_master.form.ubs == [6.0, -0.0] + @test presolve_reform.representative_master.form.lbs == [-6.0, -20.0] + @test presolve_reform.representative_master.form.ubs == [6.0, -0.0] @test presolve_reform.dw_sps[2].form.lbs == [-3.0, -10.0] @test presolve_reform.dw_sps[2].form.ubs == [3.0, -1.0] @@ -746,7 +577,7 @@ function test_col_bounds_propagation_from_restricted_master2() @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x2"]) ≈ 0 return end -register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master2) +register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master2; f = true) ## OriginalVar -> DwSpPricingVar (mapping exists) ## otherwise no propagation @@ -850,7 +681,7 @@ function test_var_bound_propagation_from_original_to_subproblem() @test result[2] == (0.5, true, Inf, false) @test result[4] == (0.3, true, Inf, false) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_subproblem) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_subproblem; f = true) ## OriginalVar -> MasterRepPricingVar (mapping exists) ## OriginalVar -> MasterPureVar (mapping exists) @@ -914,7 +745,7 @@ function test_var_bound_propagation_from_original_to_master() result = Coluna.Algorithm.bounds_tightening(orig_presolve_form.form) @test result[2] == (0.0, false, 1.0, true) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_master) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_master; f = true) ## MasterRepPricingVar -> DwSpPricingVar (mapping exists) ## otherwise no propagation @@ -1011,7 +842,7 @@ function test_var_bound_propagation_from_master_to_subproblem() result = Coluna.Algorithm.bounds_tightening(master_repr_presolve_form.form) @test result[4] == (0.3, true, Inf, false) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_master_to_subproblem) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_master_to_subproblem; f = true) ## DwSpPricingVar -> MasterRepPricingVar (mapping exists) ## otherwise no propagation @@ -1114,13 +945,13 @@ function test_var_bound_propagation_from_subproblem_to_master() result = Coluna.Algorithm.bounds_tightening(sp2_presolve_form.form) @test result[2] == (0.3, true, Inf, false) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_subproblem_to_master) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_subproblem_to_master; f = true) ############################################################################################ # Var fixing propagation. ############################################################################################ -function test_var_fixing_propagation_within_formulation1() +function test_var_fixing_propagation_within_formulation1() # TODO: fix this test # Original # max x + y + z # s.t. 2x + y + z <= 15 @@ -1155,8 +986,7 @@ function test_var_fixing_propagation_within_formulation1() new_form = Coluna.Algorithm.propagate_in_presolve_form( orig_presolve_form, Int[], - bounds_result; - store_unpropagated_partial_sol = false + bounds_result ) @test new_form.form.col_major_coef_matrix == [1 1;] @@ -1180,9 +1010,9 @@ function test_var_fixing_propagation_within_formulation1() @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) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation1; x = true) -function test_var_fixing_propagation_within_formulation2() +function test_var_fixing_propagation_within_formulation2() # TODO: fix this test # Original # max x + y + z # s.t. 2x + y + z >= 15 # test with rhs = 1 ==> bug @@ -1217,8 +1047,7 @@ function test_var_fixing_propagation_within_formulation2() new_form = Coluna.Algorithm.propagate_in_presolve_form( orig_presolve_form, Int[], - bounds_result; - store_unpropagated_partial_sol = false + bounds_result ) @test new_form.form.col_major_coef_matrix == [1 1;] @@ -1242,9 +1071,9 @@ function test_var_fixing_propagation_within_formulation2() @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) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation2; x = true) -function test_var_fixing_propagation_within_formulation3() +function test_var_fixing_propagation_within_formulation3() # TODO: fix this test # Original # max x + y + z # s.t. -2x + y + z >= 150 @@ -1283,8 +1112,7 @@ function test_var_fixing_propagation_within_formulation3() new_form = Coluna.Algorithm.propagate_in_presolve_form( orig_presolve_form, Int[], - bounds_result; - store_unpropagated_partial_sol = false + bounds_result ) @test new_form.form.col_major_coef_matrix == [1 1; 1 1;] @@ -1309,7 +1137,7 @@ function test_var_fixing_propagation_within_formulation3() @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) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation3; x = true) ## OriginalVar -> DwSpPricingVar (mapping exists) @@ -1317,7 +1145,7 @@ register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within function test_var_fixing_propagation_from_original_to_subproblem() end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_subproblem) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_subproblem; f = true) ## OriginalVar -> MasterRepPricingVar (mapping exists) ## OriginalVar -> MasterPureVar (mapping exists) @@ -1396,7 +1224,7 @@ function test_var_fixing_propagation_from_original_to_master() @test bounds_result[1] == (0, false, 0, true) @test length(bounds_result) == 1 end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_master) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_master; f = true) ## MasterColumns -> MasterRepPricingVar -> DwSpPricingVar ## otherwise no propagation @@ -1511,7 +1339,7 @@ function test_var_fixing_propagation_from_master_to_subproblem1() @test isempty(bounds_result) return end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem1) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem1; f = true) function test_var_fixing_propagation_from_master_to_subproblem2() # Master @@ -1626,8 +1454,7 @@ function test_var_fixing_propagation_from_master_to_subproblem2() new_master_repr_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( master_repr_presolve_form, Int[], - bounds_result; - store_unpropagated_partial_sol = false + bounds_result ) # Propagate bounds in subproblems @@ -1655,14 +1482,14 @@ function test_var_fixing_propagation_from_master_to_subproblem2() @test isempty(sp2_bounds_result) return end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem2) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem2; f = true) ## DwSpPricingVar -> MasterRepPricingVar ## otherwise no propagation function test_var_fixing_propagation_from_subproblem_to_master() # TODO end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_subproblem_to_master) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_subproblem_to_master; f = true) ################################################################################ # Update DW reformulation @@ -1672,14 +1499,15 @@ function update_master_repr_formulation() # min x1 + x2 + y1 + y2 + MC1 + MC2 + MC3 + MC4 + a1 + a2 # s.t. x1 + x2 + y1 + y2 + 2MC1 + MC2 + MC3 + MC4 + a1 >= 4 # 2x1 + x2 + 3y2 + 3MC1 + 2MC2 + 3MC3 + a2 >= 4 + # MC1 + MC2 + MC3 + MC4 # 0 <= x1 <= 1 # 0 <= x2 <= 1 # 0 <= y1 <= 1 # 0 <= y2 <= 1 - # 0 <= MC1 <= 1 - # 0 <= MC2 <= 1 - # 0 <= MC3 <= 1 - # 0 <= MC4 <= 1 + # 0 <= MC1 <= Inf + # 0 <= MC2 <= Inf + # 0 <= MC3 <= Inf + # 0 <= MC4 <= Inf # a1 >= 0 # a2 >= 0 # with following columns @@ -1739,7 +1567,7 @@ function update_master_repr_formulation() master_form_coef_matrix[ClMP.getid(constr), ClMP.getid(var)] = coef end DynamicSparseArrays.closefillmode!(master_form_coef_matrix) - + master_repr_presolve_form = _presolve_formulation( ["x1", "x2", "y1", "y2"], ["c1", "c2"], @@ -1749,58 +1577,60 @@ function update_master_repr_formulation() 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, true, 1.0, false), 2 => (0.1, true, 0.5, true)); - store_unpropagated_partial_sol = false - ) - @test updated_master_repr_presolve_form.form.col_major_coef_matrix == [1 1 1;] - @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.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, 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, 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, 2.9), - ] - for (constr_name, sense, rhs) in constrs - constr = master_name_to_constr[constr_name] - @test ClMP.getcursense(master_form, constr) == sense - @test ClMP.getcurrhs(master_form, constr) == rhs - end + # updated_master_repr_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( + # master_repr_presolve_form, + # Int[2], + # Dict(1 => (1.0, true, 1.0, false), 2 => (0.1, true, 0.5, true)); + # partial_sol = false + # ) + + # @test updated_master_repr_presolve_form.form.col_major_coef_matrix == [1 1 1;] + # @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.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, partial_sol_value, deactivated + # ("x1", 0.0, 0.0, 0.0, false), # representative not in the partial solution + # ("x2", 0.0, 0.4, 0.0, false), # representative not in the partial solution + # ("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, partial_sol_value, deactivated) in vars + # var = master_name_to_var[var_name] + # @show 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, 2.9), + # ] + # for (constr_name, sense, rhs) in constrs + # constr = master_name_to_constr[constr_name] + # @test ClMP.getcursense(master_form, constr) == sense + # @test ClMP.getcurrhs(master_form, constr) == rhs + # end end -register!(unit_tests, "presolve_formulation", update_master_repr_formulation) +register!(unit_tests, "presolve_formulation", update_master_repr_formulation; f = true) function update_master_formulation() end -register!(unit_tests, "presolve_formulation", update_master_formulation; x = true) +register!(unit_tests, "presolve_formulation", update_master_formulation; f = true) function update_sp_formulation() end -register!(unit_tests, "presolve_formulation", update_sp_formulation; x = true) \ No newline at end of file +register!(unit_tests, "presolve_formulation", update_sp_formulation; f = true) \ No newline at end of file From 393dbf1f910f1d2887add2c2e7e801fa1427f685 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Tue, 24 Oct 2023 15:04:30 +0200 Subject: [PATCH 3/7] wip --- src/Algorithm/presolve/helpers.jl | 87 ++++++++++--------- src/Algorithm/presolve/interface.jl | 35 +++++--- src/MathProg/formulation.jl | 2 +- test/unit/Presolve/helpers.jl | 30 +++---- test/unit/Presolve/partial_solution.jl | 8 +- test/unit/Presolve/propagation.jl | 111 +++++++++++++------------ 6 files changed, 148 insertions(+), 125 deletions(-) diff --git a/src/Algorithm/presolve/helpers.jl b/src/Algorithm/presolve/helpers.jl index f023133be..72c8067b7 100644 --- a/src/Algorithm/presolve/helpers.jl +++ b/src/Algorithm/presolve/helpers.jl @@ -279,50 +279,59 @@ end # 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 - +function shrink_col_presolve_form_repr(form::PresolveFormRepr) + # nb_cols = form.nb_vars + # nb_rows = form.nb_constrs + # coef_matrix = form.col_major_coef_matrix + # rhs = form.rhs + # sense = form.sense + + # # 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) + return +end -# # 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 +function shrink_row_presolve_form_repr(form::PresolveFormRepr, rows_to_deactivate::Vector{Int}, lm, um) + 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 -# nb_cols -= length(fixed_vars) -# row_mask = ones(Bool, nb_rows) -# row_mask[rows_to_deactivate] .= false + 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 + return PresolveFormRepr( + coef_matrix[row_mask, :], + rhs[row_mask], + sense[row_mask], + lbs, + ubs, + partial_sol, + lm, + um + ), row_mask +end function PresolveFormRepr( presolve_form_repr::PresolveFormRepr, rows_to_deactivate::Vector{Int}, tightened_bounds::Dict{Int, Tuple{Float64, Bool, Float64, Bool}}, lm, - um; + um ) row_mask = ones(Bool, presolve_form_repr.nb_constrs) col_mask = ones(Bool, presolve_form_repr.nb_vars) @@ -331,11 +340,9 @@ function PresolveFormRepr( presolve_form_repr, tightened_bounds, lm, um ) - # if shrink - # presolve_form_repr, row_mask, col_mask, fixed_vars = shrink_presolve_form_repr( - # presolve_form_repr, rows_to_deactivate, lm, um - # ) - # end + presolve_form_repr, row_mask = shrink_row_presolve_form_repr( + presolve_form_repr, rows_to_deactivate, lm, um + ) return presolve_form_repr, row_mask, col_mask end diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index 0f522a9cf..5800ad3ce 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -484,31 +484,31 @@ end Returns the local restricted partial solution. """ -function propagate_partial_sol_into_master(presolve_reform, master, dw_pricing_sps) +function propagate_partial_sol_into_master!(presolve_reform, master, dw_pricing_sps) presolve_restricted_master = presolve_reform.restricted_master - # Step 1: create the local partial solution from the restricted master presolve representation. + # Create the local partial solution from the restricted master presolve representation. # This local partial solution must then be "fixed" & propagated. local_restr_partial_sol = get_restr_partial_sol(presolve_restricted_master) - # Step 2: compute the rhs of all constraints. + # Compute the rhs of all constraints. # Non-robust and convexity constraints rhs can only be computed using this representation. new_rhs = compute_rhs(presolve_restricted_master, local_restr_partial_sol) - # Step 3: project local partial solution on the representative master. + # Project local partial solution on the representative master. local_repr_partial_sol, nb_fixed_columns_per_sp = partial_sol_on_repr( dw_pricing_sps, presolve_reform, local_restr_partial_sol ) - # Step 4: update the multiplicity of each subproblem. + # Update the multiplicity of each subproblem. update_subproblem_multiplicities!(presolve_reform.dw_sps, nb_fixed_columns_per_sp) - # Step 5: compute new default global bounds + # Compute new default global bounds master_repr_default_global_bounds = compute_default_global_bounds( presolve_reform.representative_master, presolve_reform.dw_sps, master, dw_pricing_sps ) - # Step 6: propagate local partial solution from the representative master representation + # Propagate local partial solution from the representative master representation # into the global bounds. propagate_partial_sol_to_global_bounds!( presolve_reform.representative_master, @@ -516,7 +516,7 @@ function propagate_partial_sol_into_master(presolve_reform, master, dw_pricing_s master_repr_default_global_bounds ) - # Step 7: Update the rhs of the representative master. + # Update the rhs of the representative master. @assert length(new_rhs) == length(presolve_reform.restricted_master.form.rhs) == length(presolve_reform.representative_master.form.rhs) for (row, rhs) in enumerate(new_rhs) presolve_reform.representative_master.form.rhs[row] = rhs @@ -545,16 +545,29 @@ function presolve_iteration!(presolve_reform, master, dw_pricing_sps) return end +function deactivate_non_proper_columns!(master::Formulation{DwMaster}, dw_sps) + for (varid, var) in getvars(master) + if getduty(varid) <= MasterCol + spid = getoriginformuid(varid) + if !_column_is_proper(varid, dw_sps[spid]) + deactivate!(master, varid) + end + end + end + return +end + function _presolve_run!(presolve_reform, master, dw_pricing_sps) - # Step 1: identify the partial solution in the restricted master, compute the new rhs + # Identify the partial solution in the restricted master, compute the new rhs # of all master constraints and new global and local bounds of the representative and # subproblem variables. - local_restr_partial_sol = propagate_partial_sol_into_master( + local_restr_partial_sol = propagate_partial_sol_into_master!( presolve_reform, master, dw_pricing_sps ) + # Perform several rounds of presolve. for i in 1:3 println("**** Presolve step $i ****") presolve_iteration!(presolve_reform, master, dw_pricing_sps) @@ -571,6 +584,8 @@ function _presolve_run!(presolve_reform, master, dw_pricing_sps) dw_pricing_sps, presolve_reform ) + + deactivate_non_proper_columns!(master, dw_pricing_sps) return end diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 4defd1f07..17adca703 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -711,7 +711,7 @@ function Base.show(io::IO, form::Formulation{Duty}) where {Duty <: AbstractFormD println(io, "Formulation id = ", getuid(form)) _show_obj_fun(io, form) _show_constraints(io, form) - #_show_variables(io, form) + _show_variables(io, form) _show_partial_sol(io, form) end return diff --git a/test/unit/Presolve/helpers.jl b/test/unit/Presolve/helpers.jl index 7e479c084..54489418f 100644 --- a/test/unit/Presolve/helpers.jl +++ b/test/unit/Presolve/helpers.jl @@ -62,7 +62,7 @@ function test_presolve_builder1() @test all(form.ubs .== ubs) return end -register!(unit_tests, "presolve_helper", test_presolve_builder1; f = true) +register!(unit_tests, "presolve_helper", test_presolve_builder1; x = true) # Test rows deactivation. function test_presolve_builder2() @@ -87,9 +87,8 @@ 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; - store_unpropagated_partial_sol = false + form2, _, _ = Coluna.Algorithm.PresolveFormRepr( + form, rows_to_deactivate, tightened_bounds, 1.0, 1.0 ) @test form2.nb_vars == 7 @test form2.nb_constrs == 3 @@ -100,7 +99,7 @@ function test_presolve_builder2() @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; f = true) +register!(unit_tests, "presolve_helper", test_presolve_builder2; x = true) # Test vars fixing. function test_presolve_builder3() @@ -142,9 +141,8 @@ 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; - store_unpropagated_partial_sol = false + form2, _, _ = Coluna.Algorithm.PresolveFormRepr( + form, rows_to_deactivate, tightened_bounds, 1.0, 1.0 ) @test form2.nb_vars == 3 @test form2.nb_constrs == 6 @@ -155,7 +153,7 @@ function test_presolve_builder3() @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; f = true) +register!(unit_tests, "presolve_helper", test_presolve_builder3; x = true) # Test bound tightening. function test_presolve_builder4() @@ -183,9 +181,8 @@ 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; - store_unpropagated_partial_sol = false + form2, _, _= Coluna.Algorithm.PresolveFormRepr( + form, rows_to_deactivate, tightened_bounds, 1.0, 1.0 ) @test form2.nb_vars == 6 @test form2.nb_constrs == 6 @@ -196,7 +193,7 @@ function test_presolve_builder4() @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; f = true) +register!(unit_tests, "presolve_helper", test_presolve_builder4; x = true) function test_presolve_builder5() # 2x1 + 3x2 - 2x3 >= 2 @@ -220,9 +217,8 @@ function test_presolve_builder5() 3 => (1, true, 2, true) ) - form2, _, _, _ = Coluna.Algorithm.PresolveFormRepr( - form, rows_to_deactivate, tightened_bounds, 1.0, 1.0; - store_unpropagated_partial_sol = false + form2, _, _= Coluna.Algorithm.PresolveFormRepr( + form, rows_to_deactivate, tightened_bounds, 1.0, 1.0 ) @test form2.nb_vars == 3 @test form2.nb_constrs == 2 @@ -233,7 +229,7 @@ function test_presolve_builder5() @test all(form2.ubs .== [Inf, Inf, 1]) @test all(form2.partial_solution .== [2, 2, 1]) end -register!(unit_tests, "presolve_helper", test_presolve_builder5; f = true) +register!(unit_tests, "presolve_helper", test_presolve_builder5; x = true) function row_activity() coef_matrix = sparse([ diff --git a/test/unit/Presolve/partial_solution.jl b/test/unit/Presolve/partial_solution.jl index 8db2a8cdd..e5e40849d 100644 --- a/test/unit/Presolve/partial_solution.jl +++ b/test/unit/Presolve/partial_solution.jl @@ -40,7 +40,7 @@ function test_partial_solution1() @test form2.ubs == [4.0, 4.0] @test form2.partial_solution == [2.0, 4.0] end -register!(unit_tests, "presolve_partial_sol", test_partial_solution1; f = true) +register!(unit_tests, "presolve_partial_sol", test_partial_solution1; x = true) function test_partial_solution2() # 2x + 3y <= 5 @@ -84,7 +84,7 @@ function test_partial_solution2() @test all(form2.ubs .== [Inf, Inf]) @test all(form2.partial_solution .== [3.0, 3.0]) end -register!(unit_tests, "presolve_partial_sol", test_partial_solution2; f = true) +register!(unit_tests, "presolve_partial_sol", test_partial_solution2; x = true) function test_partial_solution3() # 2x + 3y <= 5 @@ -127,7 +127,7 @@ function test_partial_solution3() @test all(form2.ubs .== [-0.0, 8.0]) @test all(form2.partial_solution .== [-3.0, 0.0]) end -register!(unit_tests, "presolve_partial_sol", test_partial_solution3; f = true) +register!(unit_tests, "presolve_partial_sol", test_partial_solution3; x = true) function test_partial_solution4() # 2x + 3y <= 5 @@ -170,4 +170,4 @@ function test_partial_solution4() @test all(form2.ubs .== [0.0, 0.0]) @test all(form2.partial_solution .== [-1.0, -1.0]) end -register!(unit_tests, "presolve_partial_sol", test_partial_solution4; f = true) \ No newline at end of file +register!(unit_tests, "presolve_partial_sol", test_partial_solution4; x = true) \ No newline at end of file diff --git a/test/unit/Presolve/propagation.jl b/test/unit/Presolve/propagation.jl index 7fabd7872..ba59b583f 100644 --- a/test/unit/Presolve/propagation.jl +++ b/test/unit/Presolve/propagation.jl @@ -133,63 +133,68 @@ function test_var_bound_propagation_within_restricted_master() # 0 <= MC3 # a >= 0 - env = Coluna.Env{Coluna.MathProg.VarId}(Coluna.Params()) - - master_form, master_name_to_var, master_constr_to_var = _mathprog_formulation!( - env, - Coluna.MathProg.DwMaster(), - [ - # name, duty, cost, lb, ub, id - ("x1", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), - ("x2", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), - ("y1", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), - ("y2", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), - ("MC1", Coluna.MathProg.MasterCol, 1.0, 0.0, Inf, nothing, nothing), - ("MC2", Coluna.MathProg.MasterCol, 1.0, 1.0, Inf, nothing, nothing), - ("MC3", Coluna.MathProg.MasterCol, 1.0, 0.0, Inf, nothing, nothing), - ("a", Coluna.MathProg.MasterArtVar, 1.0, 0.0, Inf, nothing, nothing) - ], - [ - # name, duty, rhs, sense , id - ("c1", Coluna.MathProg.MasterMixedConstr, 1.0, ClMP.Greater, nothing, nothing), - ("c2", Coluna.MathProg.MasterConvexityConstr, 2.0, ClMP.Less, nothing, nothing), - ("c3", Coluna.MathProg.MasterConvexityConstr, 0.0, ClMP.Greater, nothing, nothing) - ] - ) + # env = Coluna.Env{Coluna.MathProg.VarId}(Coluna.Params()) + + # master_form, master_name_to_var, master_constr_to_var = _mathprog_formulation!( + # env, + # Coluna.MathProg.DwMaster(), + # [ + # # name, duty, cost, lb, ub, id + # ("x1", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), + # ("x2", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), + # ("y1", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), + # ("y2", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), + # ("MC1", Coluna.MathProg.MasterCol, 1.0, 0.0, Inf, nothing, nothing), + # ("MC2", Coluna.MathProg.MasterCol, 1.0, 1.0, Inf, nothing, nothing), + # ("MC3", Coluna.MathProg.MasterCol, 1.0, 0.0, Inf, nothing, nothing), + # ("a", Coluna.MathProg.MasterArtVar, 1.0, 0.0, Inf, nothing, nothing) + # ], + # [ + # # name, duty, rhs, sense , id + # ("c1", Coluna.MathProg.MasterMixedConstr, 1.0, ClMP.Greater, nothing, nothing), + # ("c2", Coluna.MathProg.MasterConvexityConstr, 2.0, ClMP.Less, nothing, nothing), + # ("c3", Coluna.MathProg.MasterConvexityConstr, 0.0, ClMP.Greater, nothing, nothing) + # ] + # ) - master_presolve_form = _presolve_formulation( - ["MC1", "MC2", "MC3", "a"], ["c1", "c2", "c3"], [1 1 1 1; 1 1 1 0; 1 1 1 0], master_form, master_name_to_var, master_constr_to_var - ) + # master_presolve_form = _presolve_formulation( + # ["MC1", "MC2", "MC3", "a"], + # ["c1", "c2", "c3"], + # [1 1 1 1; 1 1 1 0; 1 1 1 0], + # master_form, + # master_name_to_var, + # master_constr_to_var + # ) - result = Coluna.Algorithm.bounds_tightening(master_presolve_form.form) - new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( - master_presolve_form, - Int[], - result - ) - 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 - ) + # result = Coluna.Algorithm.bounds_tightening(master_presolve_form.form) + # new_master_presolve_form = Coluna.Algorithm.propagate_in_presolve_form( + # master_presolve_form, + # Int[], + # result + # ) + # 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 + # ) - Coluna.Algorithm.update_form_from_presolve!(master_form, new_master_presolve_form) + # Coluna.Algorithm.update_form_from_presolve!(master_form, new_master_presolve_form) - @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["MC1"]) == 0.0 - @test Coluna.MathProg.getcurub(master_form, master_name_to_var["MC1"]) == Inf - @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["MC2"]) == 0.0 - @test Coluna.MathProg.getcurub(master_form, master_name_to_var["MC2"]) == Inf - @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["MC3"]) == 0.0 - @test Coluna.MathProg.getcurub(master_form, master_name_to_var["MC3"]) == Inf - @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["a"]) == 0.0 - @test Coluna.MathProg.getcurub(master_form, master_name_to_var["a"]) == Inf + # @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["MC1"]) == 0.0 + # @test Coluna.MathProg.getcurub(master_form, master_name_to_var["MC1"]) == Inf + # @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["MC2"]) == 0.0 + # @test Coluna.MathProg.getcurub(master_form, master_name_to_var["MC2"]) == Inf + # @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["MC3"]) == 0.0 + # @test Coluna.MathProg.getcurub(master_form, master_name_to_var["MC3"]) == Inf + # @test Coluna.MathProg.getcurlb(master_form, master_name_to_var["a"]) == 0.0 + # @test Coluna.MathProg.getcurub(master_form, master_name_to_var["a"]) == Inf - @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c1"]) == 0.0 - @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c2"]) == 1.0 - @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c3"]) == 0.0 + # @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c1"]) == 0.0 + # @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c2"]) == 1.0 + # @test Coluna.MathProg.getcurrhs(master_form, master_constr_to_var["c3"]) == 0.0 - @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC1"]) == 0.0 - @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC2"]) == 1.0 - @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC3"]) == 0.0 + # @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC1"]) == 0.0 + # @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC2"]) == 1.0 + # @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC3"]) == 0.0 end register!(unit_tests, "presolve_propagation", test_var_bound_propagation_within_restricted_master; f = true) @@ -309,7 +314,7 @@ function test_col_bounds_propagation_from_restricted_master() @test presolve_reform.dw_sps[2].form.lbs == [1, 1] @test presolve_reform.dw_sps[2].form.ubs == [2, 3] - local_restr_partial_sol = Coluna.Algorithm.propagate_partial_sol_into_master( + local_restr_partial_sol = Coluna.Algorithm.propagate_partial_sol_into_master!( presolve_reform, master_form, dw_pricing_sps @@ -495,7 +500,7 @@ function test_col_bounds_propagation_from_restricted_master2() ) ) - local_restr_partial_sol = Coluna.Algorithm.propagate_partial_sol_into_master( + local_restr_partial_sol = Coluna.Algorithm.propagate_partial_sol_into_master!( presolve_reform, master_form, dw_pricing_sps From dd9ace05ee725d4c07a48d793a47f3c3bdac0d05 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Tue, 24 Oct 2023 15:18:03 +0200 Subject: [PATCH 4/7] do not deactivate column of partial sol --- src/Algorithm/presolve/interface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index 5800ad3ce..f82872db7 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -549,7 +549,7 @@ function deactivate_non_proper_columns!(master::Formulation{DwMaster}, dw_sps) for (varid, var) in getvars(master) if getduty(varid) <= MasterCol spid = getoriginformuid(varid) - if !_column_is_proper(varid, dw_sps[spid]) + if !_column_is_proper(varid, dw_sps[spid]) && !Coluna.MathProg.in_partial_sol(master, varid) deactivate!(master, varid) end end From 585d5d7aba0be306ca9f843d55b27b1b9c2e4691 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Wed, 25 Oct 2023 11:08:29 +0200 Subject: [PATCH 5/7] unprint form --- src/Algorithm/presolve/interface.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index f82872db7..997b16186 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -549,8 +549,10 @@ function deactivate_non_proper_columns!(master::Formulation{DwMaster}, dw_sps) for (varid, var) in getvars(master) if getduty(varid) <= MasterCol spid = getoriginformuid(varid) - if !_column_is_proper(varid, dw_sps[spid]) && !Coluna.MathProg.in_partial_sol(master, varid) - deactivate!(master, varid) + if !_column_is_proper(varid, dw_sps[spid]) + if !in_partial_solution(master, varid) + deactivate!(master, varid) + end end end end From 9c44c7529e5cf2434b094db8adb4f99f31bedf3a Mon Sep 17 00:00:00 2001 From: Ruslan Sadykov <41117501+rrsadykov@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:30:45 +0200 Subject: [PATCH 6/7] - Updated MasterColumnsUnit, StaticVarConstrUnit. (#1103) - Added PartialSolutionUnit --- src/Algorithm/basic/solveipform.jl | 1 + src/Algorithm/basic/solvelpform.jl | 1 + src/Algorithm/benders.jl | 1 + src/Algorithm/colgen.jl | 1 + src/Algorithm/formstorages.jl | 102 +++++++---- src/Algorithm/presolve/interface.jl | 12 +- src/MathProg/varconstr.jl | 20 +-- test/unit/Algorithm/record_mastercolumns.jl | 40 +---- test/unit/Algorithm/record_partialsolution.jl | 78 +++++++++ test/unit/Algorithm/record_staticvarconstr.jl | 159 ++---------------- 10 files changed, 196 insertions(+), 219 deletions(-) create mode 100644 test/unit/Algorithm/record_partialsolution.jl diff --git a/src/Algorithm/basic/solveipform.jl b/src/Algorithm/basic/solveipform.jl index e2781e18c..412faa7e4 100644 --- a/src/Algorithm/basic/solveipform.jl +++ b/src/Algorithm/basic/solveipform.jl @@ -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 diff --git a/src/Algorithm/basic/solvelpform.jl b/src/Algorithm/basic/solvelpform.jl index efb507d9f..64e0be9db 100644 --- a/src/Algorithm/basic/solvelpform.jl +++ b/src/Algorithm/basic/solvelpform.jl @@ -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 diff --git a/src/Algorithm/benders.jl b/src/Algorithm/benders.jl index dcc126129..e93a80b50 100644 --- a/src/Algorithm/benders.jl +++ b/src/Algorithm/benders.jl @@ -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) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index 1b01bbac4..0333122bc 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -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 diff --git a/src/Algorithm/formstorages.jl b/src/Algorithm/formstorages.jl index 09ff2af3a..0b75cc626 100644 --- a/src/Algorithm/formstorages.jl +++ b/src/Algorithm/formstorages.jl @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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)) @@ -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 @@ -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 @@ -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 diff --git a/src/Algorithm/presolve/interface.jl b/src/Algorithm/presolve/interface.jl index 997b16186..111f22a28 100644 --- a/src/Algorithm/presolve/interface.jl +++ b/src/Algorithm/presolve/interface.jl @@ -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) @@ -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)) @@ -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 @@ -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 diff --git a/src/MathProg/varconstr.jl b/src/MathProg/varconstr.jl index b9e780e21..02f8056bb 100644 --- a/src/MathProg/varconstr.jl +++ b/src/MathProg/varconstr.jl @@ -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) @@ -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) @@ -244,7 +244,7 @@ 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) @@ -252,7 +252,7 @@ function add_to_partial_solution!(form::Formulation, var::Variable, value, propa 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 @@ -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 diff --git a/test/unit/Algorithm/record_mastercolumns.jl b/test/unit/Algorithm/record_mastercolumns.jl index 05856c0dd..c78cd9442 100644 --- a/test/unit/Algorithm/record_mastercolumns.jl +++ b/test/unit/Algorithm/record_mastercolumns.jl @@ -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()) @@ -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) \ No newline at end of file +register!(unit_tests, "storage_record", unit_master_columns_record, f = true) \ No newline at end of file diff --git a/test/unit/Algorithm/record_partialsolution.jl b/test/unit/Algorithm/record_partialsolution.jl new file mode 100644 index 000000000..5e48a8a18 --- /dev/null +++ b/test/unit/Algorithm/record_partialsolution.jl @@ -0,0 +1,78 @@ +function unit_partial_solution_record() + env = CL.Env{ClMP.VarId}(CL.Params()) + + # Create the following formulation: + # min 1*v1 + 2*v2 + 4*v3 + # c1: 2*v1 + v3 >= 4 + # c2: v1 + 2*v2 >= 5 + # c3: v1 + v2 + v3 >= 3 + # 0 <= v1 <= 20 + # -10 <= v2 <= 10 + # -20 <= v3 <= 0 + + form = ClMP.create_formulation!(env, ClMP.DwMaster()) + vars = Dict{String,ClMP.Variable}() + constrs = Dict{String,ClMP.Constraint}() + + rhs = [4,5,3] + for i in 1:3 + c = ClMP.setconstr!(form, "c$i", ClMP.OriginalConstr; rhs = rhs[i], sense = ClMP.Less) + constrs["c$i"] = c + end + + members = [ + Dict(ClMP.getid(constrs["c1"]) => 2.0, ClMP.getid(constrs["c2"]) => 1.0, ClMP.getid(constrs["c3"]) => 1.0), + Dict(ClMP.getid(constrs["c2"]) => 2.0, ClMP.getid(constrs["c3"]) => 1.0), + Dict(ClMP.getid(constrs["c1"]) => 1.0, ClMP.getid(constrs["c3"]) => 1.0), + ] + costs = [1,2,4] + ubounds = [20,10,0] + lbounds = [0,-10,-20] + for i in 1:3 + v = ClMP.setvar!(form, "v$i", ClMP.OriginalVar; cost = costs[i], members = members[i], lb = lbounds[i], ub = ubounds[i]) + vars["v$i"] = v + end + DynamicSparseArrays.closefillmode!(ClMP.getcoefmatrix(form)) + + storage = ClB.getstorage(form) + r1 = ClB.create_record(storage, ClA.PartialSolutionUnit) + + @test isempty(r1.partial_solution) + + # make changes on the formulation + ClMP.add_to_partial_solution!(form, vars["v1"], 5.0, true) # we propagate to bounds + ClMP.add_to_partial_solution!(form, vars["v2"], -1.0, true) + ClMP.add_to_partial_solution!(form, vars["v3"], -2.0, true) + + @test ClMP.get_value_in_partial_sol(form, vars["v1"]) == 5 + @test ClMP.get_value_in_partial_sol(form, vars["v2"]) == -1 + @test ClMP.get_value_in_partial_sol(form, vars["v3"]) == -2 + @test ClMP.getcurlb(form, vars["v1"]) == 0 + @test ClMP.getcurlb(form, vars["v2"]) == -9 + @test ClMP.getcurlb(form, vars["v3"]) == -18 + @test ClMP.getcurub(form, vars["v1"]) == 15 + @test ClMP.getcurub(form, vars["v2"]) == 0 + @test ClMP.getcurub(form, vars["v3"]) == 0 + @test ClMP.in_partial_sol(form, vars["v1"]) + @test ClMP.in_partial_sol(form, vars["v2"]) + @test ClMP.in_partial_sol(form, vars["v3"]) + + r2 = ClB.create_record(storage, ClA.PartialSolutionUnit) + + @test isempty(setdiff(keys(r2.partial_solution), ClMP.getid.(values(vars)))) + @test r2.partial_solution[ClMP.getid(vars["v1"])] == 5.0 + @test r2.partial_solution[ClMP.getid(vars["v2"])] == -1.0 + @test r2.partial_solution[ClMP.getid(vars["v3"])] == -2.0 + + ClB.restore_from_record!(storage, r1) + + @test !ClMP.in_partial_sol(form, vars["v1"]) + @test !ClMP.in_partial_sol(form, vars["v2"]) + @test !ClMP.in_partial_sol(form, vars["v3"]) + + ClB.restore_from_record!(storage, r2) + @test ClMP.get_value_in_partial_sol(form, vars["v1"]) == 5.0 + @test ClMP.get_value_in_partial_sol(form, vars["v2"]) == -1.0 + @test ClMP.get_value_in_partial_sol(form, vars["v3"]) == -2.0 +end +register!(unit_tests, "storage_record", unit_partial_solution_record, f = true) \ No newline at end of file diff --git a/test/unit/Algorithm/record_staticvarconstr.jl b/test/unit/Algorithm/record_staticvarconstr.jl index c38a5f336..16a456bdf 100644 --- a/test/unit/Algorithm/record_staticvarconstr.jl +++ b/test/unit/Algorithm/record_staticvarconstr.jl @@ -1,16 +1,14 @@ -function unit_static_var_constr_record1() - function test_var_record(state, cost, lb, ub, partial_sol_value) +function unit_static_var_constr_record() + function test_var_record(state, cost, lb, ub) @test state.cost == cost @test state.lb == lb @test state.ub == ub - @test state.partial_sol_value == partial_sol_value end - function test_var(form, var, cost, lb, ub, partial_sol_value) + 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 - @test ClMP.get_value_in_partial_sol(form, var) == partial_sol_value end function test_constr_record(state, rhs) @@ -60,9 +58,9 @@ function unit_static_var_constr_record1() @test isempty(setdiff(keys(r1.vars), ClMP.getid.(values(vars)))) @test isempty(setdiff(keys(r1.constrs), ClMP.getid.(values(constrs)))) - test_var_record(r1.vars[ClMP.getid(vars["v1"])], 1, 0, 10, 0) - test_var_record(r1.vars[ClMP.getid(vars["v2"])], 2, 0, 20, 0) - test_var_record(r1.vars[ClMP.getid(vars["v3"])], 4, 0, 30, 0) + test_var_record(r1.vars[ClMP.getid(vars["v1"])], 1, 0, 10) + test_var_record(r1.vars[ClMP.getid(vars["v2"])], 2, 0, 20) + test_var_record(r1.vars[ClMP.getid(vars["v3"])], 4, 0, 30) test_constr_record(r1.constrs[ClMP.getid(constrs["c1"])], 4) test_constr_record(r1.constrs[ClMP.getid(constrs["c2"])], 5) test_constr_record(r1.constrs[ClMP.getid(constrs["c3"])], 3) @@ -78,151 +76,26 @@ function unit_static_var_constr_record1() @test isempty(setdiff(keys(r2.vars), ClMP.getid.(values(vars)))) @test length(r2.constrs) == 2 - test_var_record(r2.vars[ClMP.getid(vars["v1"])], 1, 5, 10, 0) - test_var_record(r2.vars[ClMP.getid(vars["v2"])], 2, 0, 12, 0) - test_var_record(r2.vars[ClMP.getid(vars["v3"])], 4.6, 0, 30, 0) + test_var_record(r2.vars[ClMP.getid(vars["v1"])], 1, 5, 10) + test_var_record(r2.vars[ClMP.getid(vars["v2"])], 2, 0, 12) + test_var_record(r2.vars[ClMP.getid(vars["v3"])], 4.6, 0, 30) test_constr_record(r2.constrs[ClMP.getid(constrs["c1"])], 1) test_constr_record(r2.constrs[ClMP.getid(constrs["c3"])], 3) ClB.restore_from_record!(storage, r1) - test_var(form, vars["v1"], 1, 0, 10, 0) - test_var(form, vars["v2"], 2, 0, 20, 0) - test_var(form, vars["v3"], 4, 0, 30, 0) + test_var(form, vars["v1"], 1, 0, 10) + test_var(form, vars["v2"], 2, 0, 20) + test_var(form, vars["v3"], 4, 0, 30) test_constr(form, constrs["c1"], 4) @test ClMP.iscuractive(form, constrs["c2"]) ClB.restore_from_record!(storage, r2) - test_var(form, vars["v1"], 1, 5, 10, 0) - test_var(form, vars["v2"], 2, 0, 12, 0) - test_var(form, vars["v3"], 4.6, 0, 30, 0) + test_var(form, vars["v1"], 1, 5, 10) + test_var(form, vars["v2"], 2, 0, 12) + test_var(form, vars["v3"], 4.6, 0, 30) test_constr(form, constrs["c1"], 1) @test !ClMP.iscuractive(form, constrs["c2"]) end -register!(unit_tests, "master_columns_record", unit_static_var_constr_record1) - -# Partial solution. -function unit_static_var_constr_record2() - function test_var_record(state, cost, lb, ub, partial_sol_value) - @test state.cost == cost - @test state.lb == lb - @test state.ub == ub - @test state.partial_sol_value == partial_sol_value - end - - function test_var(form, var, cost, lb, ub, partial_sol_value) - @test ClMP.getcurcost(form, var) == cost - @test ClMP.getcurlb(form, var) == lb - @test ClMP.getcurub(form, var) == ub - @test ClMP.get_value_in_partial_sol(form, var) == partial_sol_value - end - - function test_constr_record(state, rhs) - @test state.rhs == rhs - end - - function test_constr(form, constr, rhs) - @test ClMP.getcurrhs(form, constr) == rhs - end - - env = CL.Env{ClMP.VarId}(CL.Params()) - - # Create the following formulation: - # min 1*v1 + 2*v2 + 4*v3 - # c1: 2*v1 + v3 >= 4 - # c2: v1 + 2*v2 >= 5 - # c3: v1 + v2 + v3 >= 3 - # 0 <= v1 <= 20 - # -10 <= v2 <= 10 - # -20 <= v3 <= 0 - - form = ClMP.create_formulation!(env, ClMP.DwMaster()) - vars = Dict{String,ClMP.Variable}() - constrs = Dict{String,ClMP.Constraint}() - - rhs = [4,5,3] - for i in 1:3 - c = ClMP.setconstr!(form, "c$i", ClMP.OriginalConstr; rhs = rhs[i], sense = ClMP.Less) - constrs["c$i"] = c - end - - members = [ - Dict(ClMP.getid(constrs["c1"]) => 2.0, ClMP.getid(constrs["c2"]) => 1.0, ClMP.getid(constrs["c3"]) => 1.0), - Dict(ClMP.getid(constrs["c2"]) => 2.0, ClMP.getid(constrs["c3"]) => 1.0), - Dict(ClMP.getid(constrs["c1"]) => 1.0, ClMP.getid(constrs["c3"]) => 1.0), - ] - costs = [1,2,4] - ubounds = [20,10,0] - lbounds = [0,-10,-20] - for i in 1:3 - v = ClMP.setvar!(form, "v$i", ClMP.OriginalVar; cost = costs[i], members = members[i], lb = lbounds[i], ub = ubounds[i]) - vars["v$i"] = v - end - DynamicSparseArrays.closefillmode!(ClMP.getcoefmatrix(form)) - - storage = ClB.getstorage(form) - r1 = ClB.create_record(storage, ClA.StaticVarConstrUnit) - - @test isempty(setdiff(keys(r1.vars), ClMP.getid.(values(vars)))) - @test isempty(setdiff(keys(r1.constrs), ClMP.getid.(values(constrs)))) - test_var_record(r1.vars[ClMP.getid(vars["v1"])], 1, 0, 20, 0) - test_var_record(r1.vars[ClMP.getid(vars["v2"])], 2, -10, 10, 0) - test_var_record(r1.vars[ClMP.getid(vars["v3"])], 4, -20, 0, 0) - test_constr_record(r1.constrs[ClMP.getid(constrs["c1"])], 4) - test_constr_record(r1.constrs[ClMP.getid(constrs["c2"])], 5) - test_constr_record(r1.constrs[ClMP.getid(constrs["c3"])], 3) - - # make changes on the formulation - ClMP.add_to_partial_solution!(form, vars["v1"], 5.0, true) # we propagate to bounds - ClMP.add_to_partial_solution!(form, vars["v2"], -1.0, true) - ClMP.add_to_partial_solution!(form, vars["v3"], -2.0, true) - - @test ClMP.get_value_in_partial_sol(form, vars["v1"]) == 5 - @test ClMP.get_value_in_partial_sol(form, vars["v2"]) == -1 - @test ClMP.get_value_in_partial_sol(form, vars["v3"]) == -2 - @test ClMP.getcurlb(form, vars["v1"]) == 0 - @test ClMP.getcurlb(form, vars["v2"]) == -9 - @test ClMP.getcurlb(form, vars["v3"]) == -18 - @test ClMP.getcurub(form, vars["v1"]) == 15 - @test ClMP.getcurub(form, vars["v2"]) == 0 - @test ClMP.getcurub(form, vars["v3"]) == 0 - @test ClMP.in_partial_sol(form, vars["v1"]) - @test ClMP.in_partial_sol(form, vars["v2"]) - @test ClMP.in_partial_sol(form, vars["v3"]) - - r2 = ClB.create_record(storage, ClA.StaticVarConstrUnit) - - @test isempty(setdiff(keys(r2.vars), ClMP.getid.(values(vars)))) - test_var_record(r2.vars[ClMP.getid(vars["v1"])], 1, 0, 15, 5) - test_var_record(r2.vars[ClMP.getid(vars["v2"])], 2, -9, 0, -1) - test_var_record(r2.vars[ClMP.getid(vars["v3"])], 4, -18, 0, -2) - test_constr_record(r1.constrs[ClMP.getid(constrs["c1"])], 4) - test_constr_record(r1.constrs[ClMP.getid(constrs["c2"])], 5) - test_constr_record(r1.constrs[ClMP.getid(constrs["c3"])], 3) - - ClB.restore_from_record!(storage, r1) - - test_var(form, vars["v1"], 1, 0, 20, 0) - test_var(form, vars["v2"], 2, -10, 10, 0) - test_var(form, vars["v3"], 4, -20, 0, 0) - test_constr(form, constrs["c1"], 4) - test_constr(form, constrs["c2"], 5) - test_constr(form, constrs["c3"], 3) - @test !ClMP.in_partial_sol(form, vars["v1"]) - @test !ClMP.in_partial_sol(form, vars["v2"]) - @test !ClMP.in_partial_sol(form, vars["v3"]) - - ClB.restore_from_record!(storage, r2) - - test_var(form, vars["v1"], 1, 0, 15, 5) - test_var(form, vars["v2"], 2, -9, 0, -1) - test_var(form, vars["v3"], 4, -18, 0, -2) - test_constr(form, constrs["c1"], 4) - test_constr(form, constrs["c2"], 5) - test_constr(form, constrs["c3"], 3) - @test ClMP.in_partial_sol(form, vars["v1"]) - @test ClMP.in_partial_sol(form, vars["v2"]) - @test ClMP.in_partial_sol(form, vars["v3"]) -end -register!(unit_tests, "master_columns_record", unit_static_var_constr_record2) \ No newline at end of file +register!(unit_tests, "storage_record", unit_static_var_constr_record, f = true) \ No newline at end of file From 9b9ec9299189751561497e158424f9a6a754dd7c Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Wed, 25 Oct 2023 15:37:54 +0200 Subject: [PATCH 7/7] clean tests --- test/revise.jl | 1 + test/unit/Algorithm/record_mastercolumns.jl | 2 +- test/unit/Algorithm/record_partialsolution.jl | 2 +- test/unit/Algorithm/record_staticvarconstr.jl | 2 +- test/unit/MathProg/variables.jl | 8 +- test/unit/Presolve/build_formulation.jl | 6 +- test/unit/Presolve/columns.jl | 2 +- test/unit/Presolve/helpers.jl | 46 ++--- test/unit/Presolve/propagation.jl | 157 ++---------------- 9 files changed, 45 insertions(+), 181 deletions(-) diff --git a/test/revise.jl b/test/revise.jl index c43a31b83..8e5624597 100644 --- a/test/revise.jl +++ b/test/revise.jl @@ -19,6 +19,7 @@ typical_test_dirs = [ joinpath("integration", "custom_data"), joinpath("integration", "parser"), joinpath("integration", "pricing_callback"), + joinpath("integration", "MOI"), joinpath("e2e", "gap"), joinpath("e2e_extra", "advanced_colgen"), joinpath("e2e_extra", "gap") diff --git a/test/unit/Algorithm/record_mastercolumns.jl b/test/unit/Algorithm/record_mastercolumns.jl index c78cd9442..d0a9ba85d 100644 --- a/test/unit/Algorithm/record_mastercolumns.jl +++ b/test/unit/Algorithm/record_mastercolumns.jl @@ -51,4 +51,4 @@ function unit_master_columns_record() active_varids = filter(var_id -> iscuractive(form, var_id), keys(ClMP.getvars(form))) @test isempty(setdiff(active_varids, v1v3)) end -register!(unit_tests, "storage_record", unit_master_columns_record, f = true) \ No newline at end of file +register!(unit_tests, "storage_record", unit_master_columns_record) \ No newline at end of file diff --git a/test/unit/Algorithm/record_partialsolution.jl b/test/unit/Algorithm/record_partialsolution.jl index 5e48a8a18..4b4753dbd 100644 --- a/test/unit/Algorithm/record_partialsolution.jl +++ b/test/unit/Algorithm/record_partialsolution.jl @@ -75,4 +75,4 @@ function unit_partial_solution_record() @test ClMP.get_value_in_partial_sol(form, vars["v2"]) == -1.0 @test ClMP.get_value_in_partial_sol(form, vars["v3"]) == -2.0 end -register!(unit_tests, "storage_record", unit_partial_solution_record, f = true) \ No newline at end of file +register!(unit_tests, "storage_record", unit_partial_solution_record) \ No newline at end of file diff --git a/test/unit/Algorithm/record_staticvarconstr.jl b/test/unit/Algorithm/record_staticvarconstr.jl index 16a456bdf..374a88236 100644 --- a/test/unit/Algorithm/record_staticvarconstr.jl +++ b/test/unit/Algorithm/record_staticvarconstr.jl @@ -98,4 +98,4 @@ function unit_static_var_constr_record() test_constr(form, constrs["c1"], 1) @test !ClMP.iscuractive(form, constrs["c2"]) end -register!(unit_tests, "storage_record", unit_static_var_constr_record, f = true) \ No newline at end of file +register!(unit_tests, "storage_record", unit_static_var_constr_record) \ No newline at end of file diff --git a/test/unit/MathProg/variables.jl b/test/unit/MathProg/variables.jl index e85f107b1..406264e77 100644 --- a/test/unit/MathProg/variables.jl +++ b/test/unit/MathProg/variables.jl @@ -182,12 +182,12 @@ function add_in_partial_sol_variable_2() varid = ClMP.getid(var) ClMP.deactivate!(form, varid) @test !ClMP.iscuractive(form, varid) - ClMP.add_to_partial_solution!(form, var, -1.0, true) # try an unactive variable -> should not work. - @test ClMP.getcurub(form, var) == 3 - @test ClMP.getcurlb(form, var) == -3 + ClMP.add_to_partial_solution!(form, var, -1.0, true) # try an unactive variable -> should work. + @test ClMP.getcurub(form, var) == 0 + @test ClMP.getcurlb(form, var) == -2 @test ClMP.getperenub(form, var) == 3 @test ClMP.getperenlb(form, var) == -3 - @test ClMP.get_value_in_partial_sol(form, var) == 0 + @test ClMP.get_value_in_partial_sol(form, var) == -1 end register!(unit_tests, "variables", add_in_partial_sol_variable_2) diff --git a/test/unit/Presolve/build_formulation.jl b/test/unit/Presolve/build_formulation.jl index 7993e93e5..d44e9ed29 100644 --- a/test/unit/Presolve/build_formulation.jl +++ b/test/unit/Presolve/build_formulation.jl @@ -344,7 +344,7 @@ function build_dw_presolve_reformulation() @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_16"]] == 1.0 @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_17"]] == 1.0 end -register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation; f = true) +register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation) function presolve_toy_gap_with_penalties2() form = """ @@ -692,7 +692,7 @@ function build_dw_presolve_reformulation2() @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_16"]] == 1.0 @test presolve_dw_sp.form.col_major_coef_matrix[sp_constr_ids["sp_c2"], sp_var_ids["x_17"]] == 1.0 end -register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation2; f = true) +register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation2) function presolve_reformulation_with_var_not_in_coeff_matrix() form = """ @@ -825,4 +825,4 @@ function build_dw_presolve_reformulation_with_var_not_in_coeff_matrix() @test presolve_original_master.form.col_major_coef_matrix[c, v] == val end end -register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation_with_var_not_in_coeff_matrix; f = true) +register!(unit_tests, "presolve_reformulation", build_dw_presolve_reformulation_with_var_not_in_coeff_matrix) diff --git a/test/unit/Presolve/columns.jl b/test/unit/Presolve/columns.jl index 6409f6dba..bd845eba1 100644 --- a/test/unit/Presolve/columns.jl +++ b/test/unit/Presolve/columns.jl @@ -89,4 +89,4 @@ function test_non_proper_column1() return end -register!(unit_tests, "columns", test_non_proper_column1; f = true) \ No newline at end of file +register!(unit_tests, "columns", test_non_proper_column1) \ No newline at end of file diff --git a/test/unit/Presolve/helpers.jl b/test/unit/Presolve/helpers.jl index 54489418f..e280b2e0f 100644 --- a/test/unit/Presolve/helpers.jl +++ b/test/unit/Presolve/helpers.jl @@ -15,7 +15,7 @@ function test_lb_precision() @test Coluna.Algorithm._lb_prec(e) == 0.3 @test Coluna.Algorithm._lb_prec(f) == 0.3 end -register!(unit_tests, "presolve_helper", test_lb_precision; f = true) +register!(unit_tests, "presolve_helper", test_lb_precision) function test_ub_precision() z = 1.20000000000000001 @@ -34,7 +34,7 @@ function test_ub_precision() @test Coluna.Algorithm._ub_prec(e) == 0.3 @test Coluna.Algorithm._ub_prec(f) == 0.3 end -register!(unit_tests, "presolve_helper", test_ub_precision; f = true) +register!(unit_tests, "presolve_helper", test_ub_precision) function test_presolve_builder1() coef_matrix = sparse([ @@ -262,7 +262,7 @@ function row_activity() @test Coluna.Algorithm.row_min_activity(form, 6) == transpose(coef_matrix[6,:]) * [lbs[1], ubs[2], lbs[3], lbs[4], 0, lbs[6], lbs[7]] # ok @test Coluna.Algorithm.row_max_activity(form, 6) == transpose(coef_matrix[6,:]) * [ubs[1], lbs[2], ubs[3], ubs[4], 0, ubs[6], ubs[7]] # ok end -register!(unit_tests, "presolve_helper", row_activity; f = true) +register!(unit_tests, "presolve_helper", row_activity) function row_slack() coef_matrix = sparse([ @@ -295,7 +295,7 @@ function row_slack() @test Coluna.Algorithm.row_min_slack(form, 6) == rhs[6] - Coluna.Algorithm.row_max_activity(form, 6) # ok @test Coluna.Algorithm.row_max_slack(form, 6) == rhs[6] - Coluna.Algorithm.row_min_activity(form, 6) # ok end -register!(unit_tests, "presolve_helper", row_slack; f = true) +register!(unit_tests, "presolve_helper", row_slack) function test_inner_unbounded_row() @test Coluna.Algorithm._unbounded_row(Less, Inf) @@ -307,7 +307,7 @@ function test_inner_unbounded_row() @test !Coluna.Algorithm._unbounded_row(Less, 15) @test !Coluna.Algorithm._unbounded_row(Greater, 15) end -register!(unit_tests, "presolve_helper", test_inner_unbounded_row; f = true) +register!(unit_tests, "presolve_helper", test_inner_unbounded_row) function test_inner_row_bounded_by_var_bounds_1() # x + y + z >= 1 @@ -339,7 +339,7 @@ function test_inner_row_bounded_by_var_bounds_1() @test !Coluna.Algorithm._row_bounded_by_var_bounds(sense, min_slack, max_slack, 1e-6) @test Coluna.Algorithm._infeasible_row(sense, min_slack, max_slack, 1e-6) end -register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_1; f = true) +register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_1) function test_inner_row_bounded_by_var_bounds_2() # x + y + z <= 9 @@ -371,7 +371,7 @@ function test_inner_row_bounded_by_var_bounds_2() @test !Coluna.Algorithm._row_bounded_by_var_bounds(sense, min_slack, max_slack, 1e-6) @test Coluna.Algorithm._infeasible_row(sense, min_slack, max_slack, 1e-6) end -register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_2; f = true) +register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_2) function test_inner_row_bounded_by_var_bounds_3() # x + y + z == 3 @@ -406,7 +406,7 @@ function test_inner_row_bounded_by_var_bounds_3() @test !Coluna.Algorithm._row_bounded_by_var_bounds(sense, min_slack, max_slack, 1e-6) @test !Coluna.Algorithm._infeasible_row(sense, min_slack, max_slack, 1e-6) end -register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_3; f = true) +register!(unit_tests, "presolve_helper", test_inner_row_bounded_by_var_bounds_3) function test_var_bounds_from_row1() # x + 2y + 3z >= 10 @@ -444,7 +444,7 @@ function test_var_bounds_from_row1() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, -1.0) @test isinf(ub) && ub > 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row1; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row1) function test_var_bounds_from_row2() # -3x + y + 2z <= 2 @@ -483,7 +483,7 @@ function test_var_bounds_from_row2() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, 3) @test isinf(ub) && ub > 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row2; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row2) function test_var_bounds_from_row3() # 2x + 3y - 4z <= 9 @@ -522,7 +522,7 @@ function test_var_bounds_from_row3() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, -2) @test ub == 1/2 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row3; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row3) function test_var_bounds_from_row4() # -2x + 2y + 3z >= 10 @@ -561,7 +561,7 @@ function test_var_bounds_from_row4() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, 2) @test ub == 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row4; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row4) function test_var_bounds_from_row5() # 2x + 3y + 4z = 5 @@ -597,7 +597,7 @@ function test_var_bounds_from_row5() ub = Coluna.Algorithm._var_ub_from_row(sense[1], min_slack, max_slack, 2) @test ub == 5/2 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row5; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row5) function test_var_bounds_from_row6() # x1 + x2 >= 1 (row 1) @@ -637,7 +637,7 @@ function test_var_bounds_from_row6() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack2, max_slack2, 1) @test ub == Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row6; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row6) function test_var_bounds_from_row7() # -2x + y + z >= 150 @@ -683,7 +683,7 @@ function test_var_bounds_from_row7() ub = Coluna.Algorithm._var_ub_from_row(sense[2], min_slack, max_slack, -1) @test isinf(ub) && ub > 0 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row7; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row7) function test_var_bounds_from_row8() # this was producing a bug # 2x + y + z <= 1 @@ -711,7 +711,7 @@ function test_var_bounds_from_row8() # this was producing a bug ub = Coluna.Algorithm._var_ub_from_row(sense[1], min_slack, max_slack, 2) @test ub == 1/2 end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row8; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row8) function test_var_bounds_from_row9() # x + y + a >= 1 @@ -749,7 +749,7 @@ function test_var_bounds_from_row9() @test result[1] === (0.0, false, 0.0, true) @test result[2] === (1.0, false, 1.0, true) end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row9; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row9) function test_var_bounds_from_row10() # y2 @@ -778,7 +778,7 @@ function test_var_bounds_from_row10() lb = Coluna.Algorithm._var_lb_from_row(sense[1], min_slack, max_slack, 1) @test lb == -Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row10; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row10) function test_var_bounds_from_row11() # - w - x + y + z = 0 @@ -806,7 +806,7 @@ function test_var_bounds_from_row11() lb = Coluna.Algorithm._var_lb_from_row(sense[1], min_slack, max_slack, -1) @test lb == -Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row11; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row11) function test_var_bounds_from_row12() # 0x + y + z <= 5 @@ -832,7 +832,7 @@ function test_var_bounds_from_row12() lb = Coluna.Algorithm._var_lb_from_row(sense[1], min_slack, max_slack, 0) @test lb == -Inf end -register!(unit_tests, "presolve_helper", test_var_bounds_from_row12; f = true) +register!(unit_tests, "presolve_helper", test_var_bounds_from_row12) function test_uninvolved_vars1() # 0x + y + z <= 5 @@ -853,7 +853,7 @@ function test_uninvolved_vars1() @test cols == [1] end -register!(unit_tests, "presolve_helper", test_uninvolved_vars1; f = true) +register!(unit_tests, "presolve_helper", test_uninvolved_vars1) function test_uninvolved_vars2() # x + y + z <= 5 @@ -874,7 +874,7 @@ function test_uninvolved_vars2() @test cols == [] end -register!(unit_tests, "presolve_helper", test_uninvolved_vars2; f = true) +register!(unit_tests, "presolve_helper", test_uninvolved_vars2) function test_uninvolved_vars3() # w, x, y, z @@ -894,4 +894,4 @@ function test_uninvolved_vars3() cols = Coluna.Algorithm.find_uninvolved_vars(form.col_major_coef_matrix) @test cols == [1, 4] end -register!(unit_tests, "presolve_helper", test_uninvolved_vars3; f = true) \ No newline at end of file +register!(unit_tests, "presolve_helper", test_uninvolved_vars3) \ No newline at end of file diff --git a/test/unit/Presolve/propagation.jl b/test/unit/Presolve/propagation.jl index ba59b583f..a38033411 100644 --- a/test/unit/Presolve/propagation.jl +++ b/test/unit/Presolve/propagation.jl @@ -196,7 +196,7 @@ function test_var_bound_propagation_within_restricted_master() # @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC2"]) == 1.0 # @test Coluna.MathProg.get_value_in_partial_sol(master_form, master_name_to_var["MC3"]) == 0.0 end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_within_restricted_master; f = true) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_within_restricted_master; x = true) function test_col_bounds_propagation_from_restricted_master() # Original Master @@ -390,7 +390,7 @@ function test_col_bounds_propagation_from_restricted_master() @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x2"]) ≈ 3 return end -register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master; f = true) +register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master) function test_col_bounds_propagation_from_restricted_master2() # Original Master @@ -582,7 +582,7 @@ function test_col_bounds_propagation_from_restricted_master2() @test Coluna.MathProg.getcurub(master_form, master_name_to_var["x2"]) ≈ 0 return end -register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master2; f = true) +register!(unit_tests, "presolve_propagation", test_col_bounds_propagation_from_restricted_master2) ## OriginalVar -> DwSpPricingVar (mapping exists) ## otherwise no propagation @@ -686,7 +686,7 @@ function test_var_bound_propagation_from_original_to_subproblem() @test result[2] == (0.5, true, Inf, false) @test result[4] == (0.3, true, Inf, false) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_subproblem; f = true) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_subproblem) ## OriginalVar -> MasterRepPricingVar (mapping exists) ## OriginalVar -> MasterPureVar (mapping exists) @@ -750,7 +750,7 @@ function test_var_bound_propagation_from_original_to_master() result = Coluna.Algorithm.bounds_tightening(orig_presolve_form.form) @test result[2] == (0.0, false, 1.0, true) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_master; f = true) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_original_to_master) ## MasterRepPricingVar -> DwSpPricingVar (mapping exists) ## otherwise no propagation @@ -847,7 +847,7 @@ function test_var_bound_propagation_from_master_to_subproblem() result = Coluna.Algorithm.bounds_tightening(master_repr_presolve_form.form) @test result[4] == (0.3, true, Inf, false) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_master_to_subproblem; f = true) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_master_to_subproblem) ## DwSpPricingVar -> MasterRepPricingVar (mapping exists) ## otherwise no propagation @@ -950,7 +950,7 @@ function test_var_bound_propagation_from_subproblem_to_master() result = Coluna.Algorithm.bounds_tightening(sp2_presolve_form.form) @test result[2] == (0.3, true, Inf, false) end -register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_subproblem_to_master; f = true) +register!(unit_tests, "presolve_propagation", test_var_bound_propagation_from_subproblem_to_master) ############################################################################################ # Var fixing propagation. @@ -1144,14 +1144,6 @@ function test_var_fixing_propagation_within_formulation3() # TODO: fix this test end register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_within_formulation3; x = true) - -## OriginalVar -> DwSpPricingVar (mapping exists) -## otherwise no propagation -function test_var_fixing_propagation_from_original_to_subproblem() - -end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_subproblem; f = true) - ## OriginalVar -> MasterRepPricingVar (mapping exists) ## OriginalVar -> MasterPureVar (mapping exists) ## otherwise no propagation @@ -1229,122 +1221,7 @@ function test_var_fixing_propagation_from_original_to_master() @test bounds_result[1] == (0, false, 0, true) @test length(bounds_result) == 1 end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_master; f = true) - -## MasterColumns -> MasterRepPricingVar -> DwSpPricingVar -## otherwise no propagation -function test_var_fixing_propagation_from_master_to_subproblem1() - # Master - # min x1 + x2 + y1 + y2 + MC1 + MC2 + MC3 + MC4 + a - # s.t. 2x1 + 2x2 + 2y1 + 2y2 + 2MC1 + 2MC2 + 2MC3 + 2MC4 + a >= 4 - # 0 <= x1 <= 0 (repr) --> (fixing x1 == 0) - # 0 <= x2 <= 1 (repr) - # 0 <= y1 <= 1 (repr) - # 1 <= y2 <= 1 (repr) --> (fixing y2 == 1) - # 0 <= MC1 <= 1 - # 0 <= MC2 <= 1 - # 0 <= MC3 <= 1 - # 0 <= MC4 <= 1 - # a >= 0 - - # with: - # - MC1 = [x1 = 0, x2 = 0] - # - MC2 = [x1 = 1, x2 = 0] (--> fixing MC2 == 0 because x1 == 0 -- propagation) - # - MC3 = [x1 = 0, x2 = 1] - # - MC4 = [x1 = 1, x2 = 1] (--> fixing MC4 == 0 because x1 == 0 -- propagation) - # - MC5 = [y1 = 0, y2 = 0] - # - MC6 = [y1 = 1, y2 = 0] - # - MC7 = [y1 = 0, y2 = 1] - # - MC8 = [y1 = 1, y2 = 1] - - # Subproblems - # min x1 + x2 - # s.t. x1 + x2 >= 1 - # 0 <= x1 <= 1 --> (fixing x1 == 0 by propagation) - # 0 <= x2 <= 1 - - # min y1 + y2 - # s.t. y1 + y2 >= 1 - # 0 <= y1 <= 1 - # 0 <= y2 <= 1 (--> fixing y2 == 1 by propagation) - - env = Coluna.Env{Coluna.MathProg.VarId}(Coluna.Params()) - - master_form, master_name_to_var, master_constr_to_var = _mathprog_formulation!( - env, - Coluna.MathProg.DwMaster(), - [ - # name, duty, cost, lb, ub, id - ("x1", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 0.0, nothing, nothing), - ("x2", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), - ("y1", Coluna.MathProg.MasterRepPricingVar, 1.0, 0.0, 1.0, nothing, nothing), - ("y2", Coluna.MathProg.MasterRepPricingVar, 1.0, 1.0, 1.0, nothing, nothing), - ("MC1", Coluna.MathProg.MasterCol, 1.0, 0.0, 1.0, nothing, nothing), - ("MC2", Coluna.MathProg.MasterCol, 1.0, 0.0, 1.0, nothing, nothing), - ("MC3", Coluna.MathProg.MasterCol, 1.0, 0.0, 1.0, nothing, nothing), - ("MC4", Coluna.MathProg.MasterCol, 1.0, 0.0, 1.0, nothing, nothing), - ("a", Coluna.MathProg.MasterArtVar, 1.0, 0.0, Inf, nothing, nothing) - ], - [ - # name, duty, rhs, sense , id - ("c1", Coluna.MathProg.MasterMixedConstr, 4.0, ClMP.Greater, nothing, nothing) - ] - ) - - master_repr_presolve_form = _presolve_formulation( - ["x1", "x2", "y1", "y2"], ["c1"], [2 2 2 2;], master_form, master_name_to_var, master_constr_to_var - ) - - master_presolve_form = _presolve_formulation( - ["MC1", "MC2", "MC3", "MC4", "a"], ["c1"], [2 2 2 2 1;], master_form, master_name_to_var, master_constr_to_var - ) - - sp1_form, sp1_name_to_var, sp1_name_to_constr = _mathprog_formulation!( - env, - Coluna.MathProg.DwSp( - nothing, nothing, nothing, ClMP.Continuous, Coluna.MathProg.Pool() - ), - [ - # name, duty, cost, lb, ub, id - ("x1", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(master_name_to_var["x1"]), nothing), - ("x2", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(master_name_to_var["x2"]), nothing) - ], - [ - # name, duty, rhs, sense, id - ("c3", Coluna.MathProg.DwSpPureConstr, 1.0, ClMP.Greater, nothing, nothing) - ] - ) - - sp1_presolve_form = _presolve_formulation( - ["x1", "x2"], ["c3"], [1 1;], sp1_form, sp1_name_to_var, sp1_name_to_constr - ) - - sp2_form, sp2_name_to_var, sp2_name_to_constr = _mathprog_formulation!( - env, - Coluna.MathProg.DwSp( - nothing, nothing, nothing, ClMP.Continuous, Coluna.MathProg.Pool() - ), - [ - # name, duty, cost, lb, ub, id - ("y1", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(master_name_to_var["y1"]), nothing), - ("y2", Coluna.MathProg.DwSpPricingVar, 1.0, 0.0, 1.0, Coluna.Algorithm.getid(master_name_to_var["y2"]), nothing) - ], - [ - # name, duty, rhs, sense, id - ("c4", Coluna.MathProg.DwSpPureConstr, 1.0, ClMP.Greater, nothing, nothing) - ] - ) - - sp2_presolve_form = _presolve_formulation( - ["y1", "y2"], ["c4"], [1 1;], sp2_form, sp2_name_to_var, sp2_name_to_constr - ) - - # Run the presolve variable fixing on the original formulation. - bounds_result = Coluna.Algorithm.bounds_tightening(master_repr_presolve_form.form) - @test isempty(bounds_result) - return -end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem1; f = true) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_original_to_master) function test_var_fixing_propagation_from_master_to_subproblem2() # Master @@ -1487,14 +1364,7 @@ function test_var_fixing_propagation_from_master_to_subproblem2() @test isempty(sp2_bounds_result) return end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem2; f = true) - -## DwSpPricingVar -> MasterRepPricingVar -## otherwise no propagation -function test_var_fixing_propagation_from_subproblem_to_master() - # TODO -end -register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_subproblem_to_master; f = true) +register!(unit_tests, "presolve_propagation", test_var_fixing_propagation_from_master_to_subproblem2) ################################################################################ # Update DW reformulation @@ -1630,12 +1500,5 @@ function update_master_repr_formulation() # @test ClMP.getcurrhs(master_form, constr) == rhs # end end -register!(unit_tests, "presolve_formulation", update_master_repr_formulation; f = true) +register!(unit_tests, "presolve_formulation", update_master_repr_formulation; x = true) -function update_master_formulation() -end -register!(unit_tests, "presolve_formulation", update_master_formulation; f = true) - -function update_sp_formulation() -end -register!(unit_tests, "presolve_formulation", update_sp_formulation; f = true) \ No newline at end of file