diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index d2c93a9dd..fa1a2c28e 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -217,6 +217,7 @@ function clear_before_colgen_iteration!(spinfo::SubprobInfo) return end +# used by stabiization only function compute_subgradient_contribution( algo::ColumnGeneration, master::Formulation, puremastervars::Vector{Pair{VarId,Float64}}, spinfos::Dict{FormId,SubprobInfo} @@ -241,6 +242,7 @@ function compute_subgradient_contribution( return sparsevec(var_ids, var_vals) end +# used in reduced_costs_of_solutions function compute_reduced_cost( stab_is_used, masterform::Formulation, spsol::PrimalSolution, lp_dual_sol::DualSolution diff --git a/src/Algorithm/colgen/default.jl b/src/Algorithm/colgen/default.jl index 54fde50a3..5286b1826 100644 --- a/src/Algorithm/colgen/default.jl +++ b/src/Algorithm/colgen/default.jl @@ -1,12 +1,40 @@ struct ColGenContext <: ColGen.AbstractColGenContext - # Information to solve the master - master_solve_alg - master_optimizer_id + reform::Reformulation + optim_sense + current_ip_primal_bound - # Memoization to compute reduced costs (this is a precompute) - redcost_mem + restr_master_solve_alg + restr_master_optimizer_id::Int + + pricing_solve_alg + + reduced_cost_helper::ReducedCostsCalculationHelper + + # # Information to solve the master + # master_solve_alg + # master_optimizer_id + + # # Memoization to compute reduced costs (this is a precompute) + # redcost_mem + function ColGenContext(reform, alg) + rch = ReducedCostsCalculationHelper(getmaster(reform)) + return new( + reform, + getobjsense(reform), + 0.0, + alg.restr_master_solve_alg, + alg.restr_master_optimizer_id, + alg.pricing_prob_solve_alg, + rch + ) + end end +ColGen.get_reform(ctx::ColGenContext) = ctx.reform +ColGen.get_master(ctx::ColGenContext) = getmaster(ctx.reform) +ColGen.get_pricing_subprobs(ctx::ColGenContext) = get_dw_pricing_sps(ctx.reform) + + ############################################################################### # Sequence of phases ############################################################################### @@ -38,6 +66,8 @@ struct ColGenPhase3 <: ColGen.AbstractColGenPhase end ## Implementation of `initial_phase`. ColGen.initial_phase(::ColunaColGenPhaseIterator) = ColGenPhase3() +colgen_mast_lp_sol_has_art_vars(ctx) = false + ## Implementation of `next_phase`. function ColGen.next_phase(::ColunaColGenPhaseIterator, ::ColGenPhase1, ctx) # If master LP solution has no artificial vars, it means that the phase 1 has succeeded. @@ -63,12 +93,7 @@ function ColGen.next_phase(::ColunaColGenPhaseIterator, ::ColGenPhase3, ctx) return nothing end -## Methods used in the implementation and that we should mock in tests. -function colgen_mast_lp_sol_has_art_vars(ctx::ColGenContext) - -end - -## Implementatation of `setup_reformulation!` +# Implementatation of `setup_reformulation!` ## Phase 1 => non-artifical variables have cost equal to 0 function ColGen.setup_reformulation!(reform, ::ColGenPhase1) master = getmaster(reform) @@ -106,59 +131,151 @@ function ColGen.setup_reformulation!(reform, ::ColGenPhase3) return end -######### Pricing strategy -struct ClassicPricingStrategy <: ColGen.AbstractPricingStrategy - subprobs::Dict{FormId, Formulation{DwSp}} -end -# function ColGen.get_pricing_strategy(ctx::ColGen.AbstractColGenContext, _) -# ClassicPricingStrategy(Dict(i => sp for (i, sp) in ColGen.get_pricing_subprobs(ctx))) -# end -# ColGen.pricing_strategy_iterate(ps::ClassicPricingStrategy) = iterate(ps.subprobs) +# Master resolution + +""" + ColGenMasterResult{F,S} +Contains the solution to the master LP. +- `F` is the formulation type +- `S` is the objective sense Type +""" +struct ColGenMasterResult{F,S} + result::OptimizationState{F,S} +end -######### Column generation +# TODO: not type stable !! +function ColGen.optimize_master_lp_problem!(master, ctx::ColGenContext, env) + rm_input = OptimizationState(master, ip_primal_bound=ctx.current_ip_primal_bound) + opt_state = run!(ctx.restr_master_solve_alg, env, master, rm_input, ctx.restr_master_optimizer_id) + return ColGenMasterResult(opt_state) +end -# Placeholder methods: -ColGen.before_colgen_iteration(::ColGenContext, _, _) = nothing -ColGen.after_colgen_iteration(::ColGenContext, _, _, _) = nothing +function ColGen.is_infeasible(master_res::ColGenMasterResult) + status = getterminationstatus(master_res.result) + return status == ClB.INFEASIBLE || status == ClB.INFEASIBLE_OR_UNBOUNDED +end -######### Column generation iteration -function ColGen.optimize_master_lp_problem!(master, context, env) - println("\e[31m optimize master lp problem \e[00m") - input = OptimizationState(master, ip_primal_bound=0.0) # TODO : ip_primal_bound=get_ip_primal_bound(cg_optstate) - return run!(context.master_solve_alg, env, master, input, context.master_optimizer_id) +function ColGen.is_unbounded(master_res::ColGenMasterResult) + status = getterminationstatus(master_res.result) + return status == ClB.DUAL_INFEASIBLE || status == ClB.INFEASIBLE_OR_UNBOUNDED end -#get_primal_sol(mast_result) +ColGen.get_primal_sol(master_res::ColGenMasterResult) = get_best_lp_primal_sol(master_res.result) +ColGen.get_dual_sol(master_res::ColGenMasterResult) = get_best_lp_dual_sol(master_res.result) +ColGen.get_obj_val(master_res::ColGenMasterResult) = get_lp_primal_bound(master_res.result) + +function ColGen.update_master_constrs_dual_vals!(ctx::ColGenContext, phase, reform, master_lp_dual_sol) -function ColGen.check_primal_ip_feasibility(ctx, mast_lp_primal_sol) - println("\e[31m check primal ip feasibility \e[00m") - return !contains(mast_lp_primal_sol, varid -> isanArtificialDuty(getduty(varid))) && - isinteger(proj_cols_on_rep(mast_lp_primal_sol, getmodel(mast_lp_primal_sol))) end -#update_inc_primal_sol! +function ColGen.check_primal_ip_feasibility(master_lp_primal_sol, phase, reform) + +end -#get_dual_sol(mast_result) +# Reduced costs calculation +ColGen.get_orig_costs(ctx::ColGenContext) = ctx.reduced_cost_helper.c +ColGen.get_coef_matrix(ctx::ColGenContext) = ctx.reduced_cost_helper.A -function ColGen.update_master_constrs_dual_vals!(ctx, master, smooth_dual_sol) - println("\e[32m update_master_constrs_dual_vals \e[00m") - # Set all dual value of all constraints to 0. - for constr in Iterators.values(getconstrs(master)) - setcurincval!(master, constr, 0.0) +function ColGen.update_sp_vars_red_costs!(ctx::ColGenContext, sp::Formulation{DwSp}, red_costs) + for (var_id, _) in getvars(sp) + setcurcost!(sp, var_id, red_costs[var_id]) end - # Update constraints that have non-zero dual values. - for (constr_id, val) in smooth_dual_sol - setcurincval!(master, constr_id, val) + return +end + +# Columns insertion +function ColGen.insert_columns!(reform, ctx::ColGenContext, phase, columns) + for column in columns + @show column end + return 1 end -function ColGen.update_sp_vars_red_costs!(ctx, sp, red_costs) - println("\e[34m update_sp_vars_red_costs \e[00m") - for (var_id, _) in getvars(sp) - setcurcost!(sp, var_id, red_costs[var_id]) +############################################################################# +# Pricing strategy +############################################################################# +struct ClassicColGenPricingStrategy <: ColGen.AbstractPricingStrategy + subprobs::Dict{FormId, Formulation{DwSp}} +end + +ColGen.get_pricing_strategy(ctx::ColGen.AbstractColGenContext, _) = ClassicColGenPricingStrategy(ColGen.get_pricing_subprobs(ctx)) +ColGen.pricing_strategy_iterate(ps::ClassicColGenPricingStrategy) = iterate(ps.subprobs) +ColGen.pricing_strategy_iterate(ps::ClassicColGenPricingStrategy, state) = iterate(ps.subprobs, state) + +############################################################################# +# Column generation +############################################################################# +function ColGen.compute_sp_init_db(ctx::ColGenContext, sp::Formulation{DwSp}) + return ctx.optim_sense == MinSense ? -Inf : Inf +end + +struct GeneratedColumn + column::PrimalSolution{Formulation{DwSp}} + red_cost::Float64 +end + +""" + A structure to store a collection of columns +""" +struct ColumnsSet + columns::Vector{GeneratedColumn} + ColumnsSet() = new(GeneratedColumn[]) +end +Base.iterate(set::ColumnsSet) = iterate(set.columns) +Base.iterate(set::ColumnsSet, state) = iterate(set.columns, state) + +function ColGen.set_of_columns(ctx::ColGenContext) + return ColumnsSet() +end + +struct ColGenPricingResult{F,S} + result::OptimizationState{F,S} + columns::Vector{GeneratedColumn} +end + +function ColGen.is_infeasible(pricing_res::ColGenPricingResult) + status = getterminationstatus(pricing_res.result) + return status == ClB.INFEASIBLE || status == ClB.INFEASIBLE_OR_UNBOUNDED +end + +function ColGen.is_unbounded(pricing_res::ColGenPricingResult) + status = getterminationstatus(pricing_res.result) + return status == ClB.DUAL_INFEASIBLE || status == ClB.INFEASIBLE_OR_UNBOUNDED +end + +ColGen.get_primal_sols(pricing_res) = pricing_res.columns +ColGen.get_dual_bound(pricing_res) = get_ip_dual_bound(pricing_res.result) + +has_improving_red_cost(column::GeneratedColumn) = column.red_cost < 0 +# In our implementation of `push_in_set!`, we keep only columns that have improving reduced +# cost. +function ColGen.push_in_set!(pool, column) + println("push_in_set!") + # We keep only columns that improve reduced cost + if has_improving_red_cost(column) + push!(pool.columns, column) end return +end + +function ColGen.optimize_pricing_problem!(ctx::ColGenContext, sp::Formulation{DwSp}, env, master_dual_sol) + input = OptimizationState(sp) + opt_state = run!(ctx.pricing_solve_alg, env, sp, input) # master & master dual sol for non robust cuts + + # Reduced cost of a column is composed of the cost of the subproblem variables + # and the contribution of the master convex combination constraints. + # TODO: find better name + # TODO; make sure duty_data is well defined. + lb_dual = 0.0 # master_dual_sol[sp.duty_data.lower_multiplicity_constr_id] + ub_dual = 0.0 # master_dual_sol[sp.duty_data.upper_multiplicity_constr_id] + generated_columns = GeneratedColumn[] + + for col in get_ip_primal_sols(opt_state) + red_cost = getvalue(col) - lb_dual - ub_dual + push!(generated_columns, GeneratedColumn(col, red_cost)) + end + return ColGenPricingResult(opt_state, generated_columns) end \ No newline at end of file diff --git a/src/Algorithm/colgen/iteration.jl b/src/Algorithm/colgen/iteration.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/Algorithm/colgen/phases.jl b/src/Algorithm/colgen/phases.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/Algorithm/colgen/pricing.jl b/src/Algorithm/colgen/pricing.jl new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/Algorithm/colgen/pricing.jl @@ -0,0 +1 @@ + diff --git a/src/Algorithm/colgen/utils.jl b/src/Algorithm/colgen/utils.jl index d2a38f945..79776fe3b 100644 --- a/src/Algorithm/colgen/utils.jl +++ b/src/Algorithm/colgen/utils.jl @@ -75,6 +75,11 @@ subproblem variables: representative variables. Calculation is `c - transpose(A) * master_lp_dual_solution`. + +This information is given to the generic implementation of the column generation algorithm +through methods: +- ColGen.get_orig_costs +- ColGen.get_orig_coefmatrix """ struct ReducedCostsCalculationHelper c::SparseVector{Float64,VarId} diff --git a/src/Algorithm/colgenstabilization.jl b/src/Algorithm/colgenstabilization.jl index 7500cb51a..03ea6df58 100644 --- a/src/Algorithm/colgenstabilization.jl +++ b/src/Algorithm/colgenstabilization.jl @@ -45,7 +45,6 @@ function ClB.restore_from_record!( return end - # function ColGenStabilizationUnit(master::Formulation) # return ColGenStabilizationUnit( # 0.5, 0.0, 0, DualBound(master), DualBound(master), nothing, nothing, nothing @@ -101,7 +100,6 @@ function update_alpha_automatically!( unit::ColGenStabilizationUnit, nb_new_col::Int64, lp_dual_sol::DualSolution{M}, smooth_dual_sol::DualSolution{M}, h, subgradient_contribution ) where {M} - master = getmodel(lp_dual_sol) # first we calculate the in-sep direction diff --git a/src/ColGen/interface.jl b/src/ColGen/interface.jl index 24282dd7f..cde2e48b5 100644 --- a/src/ColGen/interface.jl +++ b/src/ColGen/interface.jl @@ -140,7 +140,7 @@ information at two different places. Returns a primal solution expressed in the original problem variables if the current master LP solution is integer feasible; `nothing` otherwise. """ -@mustimplement "ColGenMaster" check_primal_ip_feasibility(phase, mast_lp_primal_sol, reform) +@mustimplement "ColGenMaster" check_primal_ip_feasibility(mast_lp_primal_sol, phase, reform) ############################################################################################ # Reduced costs calculation. @@ -173,7 +173,9 @@ if something unexpected happens. function check_master_termination_status(mast_result) - # TODO + if !is_infeasible(mast_result) && !is_unbounded(mast_result) + @assert !isnothing(get_dual_sol(mast_result)) + end end function check_pricing_termination_status(pricing_result) @@ -181,6 +183,7 @@ function check_pricing_termination_status(pricing_result) end function compute_dual_bound(ctx, phase, master_lp_obj_val, master_dbs) + # TODO pure master variables are missing. return master_lp_obj_val + mapreduce(((id, val),) -> val, +, master_dbs) end @@ -224,6 +227,7 @@ function run_colgen_iteration!(context, phase, env) mast_dual_sol = get_dual_sol(mast_result) if isnothing(mast_dual_sol) + error("Cannot continue") # error or stop? (depends on the context) end @@ -233,6 +237,16 @@ function run_colgen_iteration!(context, phase, env) update_master_constrs_dual_vals!(context, phase, get_reform(context), mast_dual_sol) # Stabilization + # initialize stabilisation for the iteration + # update_stab_after_rm_solve! + # stabcenter is master_dual_sol + # return alpha * stab_center + (1 - alpha) * lp_dual_sol + + + # With stabilization, you solve several times the suproblem because you can have misprice + # loop: + # - solve all subproblems + # - check if misprice # Compute reduced cost (generic operation) by you must support math operations. c = get_orig_costs(context) @@ -262,7 +276,7 @@ function run_colgen_iteration!(context, phase, env) while !isnothing(sp_to_solve_it) (sp_id, sp_to_solve), state = sp_to_solve_it - pricing_result = optimize_pricing_problem!(context, sp_to_solve) + pricing_result = optimize_pricing_problem!(context, sp_to_solve, env, mast_dual_sol) # Iteration continues only if the pricing solution is not infeasible nor unbounded. if is_infeasible(pricing_result) @@ -294,9 +308,17 @@ function run_colgen_iteration!(context, phase, env) nb_cols_inserted = insert_columns!(get_reform(context), context, phase, generated_columns) master_lp_obj_val = get_obj_val(mast_result) - db = compute_dual_bound(context, phase, master_lp_obj_val, sps_db) + + # compute valid dual bound using the dual bounds returned by the user (cf pricing result). + valid_db = compute_dual_bound(context, phase, master_lp_obj_val, sps_db) + + pseudo_db = 0 # same but using primal bound of the pricing result. + # pseudo_db used only in the stabilization (update_stability_center!) + + # update_stab_after_gencols! + # check gap - return ColGenIterationOutput(master_lp_obj_val, db, nb_cols_inserted, false, false, false, false) + return ColGenIterationOutput(master_lp_obj_val, valid_db, nb_cols_inserted, false, false, false, false) end diff --git a/src/ColGen/pricing.jl b/src/ColGen/pricing.jl index 4b2e7b9aa..ee5841634 100644 --- a/src/ColGen/pricing.jl +++ b/src/ColGen/pricing.jl @@ -44,7 +44,7 @@ provided by this method. Returns an initial dual bound for a pricing subproblem. Default value should be +/- infinite depending on the optimization sense. """ -@mustimplement "ColGenIteration" compute_sp_init_db() +@mustimplement "ColGenIteration" compute_sp_init_db(ctx) """ Returns an empty container that will store all the columns generated by the pricing problems @@ -54,14 +54,16 @@ One must be able to iterate on this container to insert the columns in the maste @mustimplement "ColGenPricing" set_of_columns(ctx) """ - optimize_pricing_problem!(ctx, sp) -> PricingResult + optimize_pricing_problem!(ctx, sp, env, mast_dual_sol) -> PricingResult Returns a custom object `PricingResult` that must implement following functions: - `get_primal_sols`: array of primal solution to the pricing subproblem - `get_primal_bound`: best reduced cost (optional ?) - `get_dual_bound`: dual bound of the pricing subproblem (used to compute the master dual bound) + +TODO: talk about master_dual_sol """ -@mustimplement "ColGenPricing" optimize_pricing_problem!(ctx, sp) +@mustimplement "ColGenPricing" optimize_pricing_problem!(ctx, sp, env, mast_dual_sol) "Array of primal solutions to the pricing subproblem" @mustimplement "ColGenPricing" get_primal_sols(pricing_res) diff --git a/test/unit/ColGen/colgen_default.jl b/test/unit/ColGen/colgen_default.jl new file mode 100644 index 000000000..1a13bacd8 --- /dev/null +++ b/test/unit/ColGen/colgen_default.jl @@ -0,0 +1,252 @@ +# Minimization and test all constraint senses +form1() = """ +master + min + 3x1 + 2x2 + 5x3 + 4y1 + 3y2 + 5y3 + s.t. + x1 + x2 + x3 + y1 + y2 + y3 >= 10 + x1 + 2x2 + y1 + 2y2 <= 100 + x1 + 3x3 + y1 + + 3y3 == 100 + + integer + representatives + x1, x2, x3, y1, y2, y3 + + bounds + x1 >= 0 + x2 >= 0 + x3 >= 0 + y1 >= 0 + y2 >= 0 + y3 >= 0 +""" + +function get_name_to_varids(form) + d = Dict{String, ClMP.VarId}() + for (varid, var) in ClMP.getvars(form) + d[ClMP.getname(form, var)] = varid + end + return d +end + +function get_name_to_constrids(form) + d = Dict{String, ClMP.ConstrId}() + for (constrid, constr) in ClMP.getconstrs(form) + d[ClMP.getname(form, constr)] = constrid + end + return d +end + +# Simple case with only subproblem representatives variables. +function test_reduced_costs_calculation_helper() + _, master, _, _, _ = reformfromstring(form1()) + vids = get_name_to_varids(master) + cids = get_name_to_constrids(master) + + helper = ClA.ReducedCostsCalculationHelper(master) + @test helper.c[vids["x1"]] == 3 + @test helper.c[vids["x2"]] == 2 + @test helper.c[vids["x3"]] == 5 + @test helper.c[vids["y1"]] == 4 + @test helper.c[vids["y2"]] == 3 + @test helper.c[vids["y3"]] == 5 + + @test helper.A[cids["c1"], vids["x1"]] == 1 + @test helper.A[cids["c1"], vids["x2"]] == 1 + @test helper.A[cids["c1"], vids["x3"]] == 1 + @test helper.A[cids["c1"], vids["y1"]] == 1 + @test helper.A[cids["c1"], vids["y2"]] == 1 + @test helper.A[cids["c1"], vids["y3"]] == 1 + + @test helper.A[cids["c2"], vids["x1"]] == 1 + @test helper.A[cids["c2"], vids["x2"]] == 2 + @test helper.A[cids["c2"], vids["y1"]] == 1 + @test helper.A[cids["c2"], vids["y2"]] == 2 + + @test helper.A[cids["c3"], vids["x1"]] == 1 + @test helper.A[cids["c3"], vids["x3"]] == 3 + @test helper.A[cids["c3"], vids["y1"]] == 1 + @test helper.A[cids["c3"], vids["y3"]] == 3 +end +register!(unit_tests, "colgen_default", test_reduced_costs_calculation_helper) + + +# All the tests are based on the Generalized Assignment problem. +# x_jm = 1 if job j is assigned to machine m +gap1() = """ + master + min + x_11 + 3x_12 + 4x_13 + 5x_14 + 6x_15 + 3x_21 + x_22 + 2x_23 + 3x_24 + 4x_25 + s.t. + x_11 + x_12 + x_13 + x_14 + x_15 >= 1 + x_21 + x_22 + x_23 + x_24 + x_25 >= 1 + + dw_sp + min + x_11 + 3x_12 + 4x_13 + 5x_14 + 6x_15 + s.t. + 2x_11 + 3x_12 + 4x_13 + 5x_14 + 6x_15 <= 15 + + dw_sp + min + 3x_21 + x_22 + 2x_23 + 3x_24 + 4x_25 + s.t. + 3x_21 + 4x_22 + 3x_23 + 2x_24 + 4x_25 <= 20 + + integer + representatives + x_11, x_12, x_13, x_14, x_15, x_21, x_22, x_23, x_24, x_25 + + bounds + 0 <= x_11 <= 1 + 0 <= x_12 <= 1 + 0 <= x_13 <= 1 + 0 <= x_14 <= 1 + 0 <= x_15 <= 1 + 0 <= x_21 <= 1 + 0 <= x_22 <= 1 + 0 <= x_23 <= 1 + 0 <= x_24 <= 1 + 0 <= x_25 <= 1 + """ + +function form2() + form = """ + master + min + 7x_12 + 2x_13 + x_14 + 5x_15 + 3x_23 + 6x_24 + 8x_25 + 4x_34 + 2x_35 + 9x_45 + 28λ1 + 25λ2 + 21λ3 + 19λ4 + 22λ5 + 18λ6 + 28λ7 + s.t. + x_12 + x_13 + x_14 + x_15 + 2λ1 + 2λ2 + 2λ3 + 2λ4 + 2λ5 + 2λ6 + 2λ7 == 2 + x_12 + x_23 + x_24 + x_25 + 2λ1 + 2λ2 + 2λ3 + 1λ4 + 1λ5 + 2λ6 + 3λ7 == 2 + x_13 + x_23 + x_34 + x_35 + 2λ1 + 3λ2 + 2λ3 + 3λ4 + 2λ5 + 3λ6 + 1λ7 == 2 + x_14 + x_24 + x_34 + x_45 + 2λ1 + 2λ2 + 3λ3 + 3λ4 + 3λ5 + 1λ6 + 1λ7 == 2 + x_15 + x_25 + x_35 + x_45 + 2λ1 + 1λ2 + 1λ3 + 1λ4 + 2λ5 + 2λ6 + 3λ7 == 2 + + dw_sp + min + 7x_12 + 2x_13 + x_14 + 5x_15 + 3x_23 + 6x_24 + 8x_25 + 4x_34 + 2x_35 + 9x_45 + s.t. + x_15 + x_25 + x_35 + x_45 + + continuous + columns + λ1, λ2, λ3, λ4, λ5, λ6, λ7 + + integer + representatives + x_12, x_13, x_14, x_15, x_23, x_24, x_25, x_34, x_35, x_45 + + bounds + λ1 >= 0 + λ2 >= 0 + λ3 >= 0 + λ4 >= 0 + λ5 >= 0 + λ6 >= 0 + λ7 >= 0 + x_12 >= 0 + x_13 >= 0 + x_14 >= 0 + x_15 >= 0 + x_23 >= 0 + x_24 >= 0 + x_25 >= 0 + x_34 >= 0 + x_35 >= 0 + x_45 >= 0 + """ + env, master, sps, _, reform = reformfromstring(form) + + setupvar = ClMP.setvar!(sps[1], "pricing_setup_var", ClMP.DwSpSetupVar; + cost = 0.0, lb = 1.0, ub = 1.0, kind = Integ, is_explicit = true + ) + setuprepvar = ClMP.setvar!(master, "pricing_setup_var", MasterRepPricingSetupVar; + cost = 0.0, lb = 1.0, ub = 1.0, kind = Integ, is_explicit = false + ) + + # λ1 + λ2 + λ3 + λ4 + λ5 + λ6 + λ7 >= 1 + lb_mult = 1.0 + name = "sp_lb_1" + lb_conv_constr = ClMP.setconstr!( + master, name, ClMP.MasterConvexityConstr; rhs = lb_mult, kind = ClMP.Essential, + sense = ClMP.Greater, inc_val = 100.0, + loc_art_var_abs_cost = 0.0 + ) + coefmatrix = ClMP.getcoefmatrix(master) + coefmatrix[getid(lb_conv_constr), getid(setuprepvar)] = 1.0 + + # λ1 + λ2 + λ3 + λ4 + λ5 + λ6 + λ7 <= 1 + ub_mult = 1.0 + name = "sp_ub_1" + ub_conv_constr = ClMP.setconstr!( + master, name, ClMP.MasterConvexityConstr; rhs = ub_mult, kind = ClMP.Essential, + sense = ClMP.Less, inc_val = 100.0, + loc_art_var_abs_cost = 0.0 + ) + coefmatrix[getid(ub_conv_constr), getid(setuprepvar)] = 1.0 + + return env, master, sps, reform +end + +function test_colgen_iteration() + env, master, sps, reform = form2() + + @show master + + # vids = get_name_to_varids(master) + # cids = get_name_to_constrids(master) + + ctx = ClA.ColGenContext(reform, ClA.ColumnGeneration()) + ClMP.push_optimizer!(master, () -> ClA.MoiOptimizer(GLPK.Optimizer())) + ClMP.relax_integrality!(master) + for sp in sps + ClMP.push_optimizer!(sp, () -> ClA.MoiOptimizer(GLPK.Optimizer())) + end + ColGen.run_colgen_iteration!(ctx, ClA.ColGenPhase3(), env) +end +register!(unit_tests, "colgen_default", test_colgen_iteration) + + + +# master +# min +# 7x_12 + 2x_13 + x_14 + 5x_15 + 3x_23 + 6x_24 + 8x_25 + 4x_34 + 2x_35 + 9x_45 + 28λ1 + 25λ2 + 21λ3 + 19λ4 + 22λ5 + 18λ6 + 28λ7 +# s.t. +# x_12 + x_13 + x_14 + x_15 + 2λ1 + 2λ2 + 2λ3 + 2λ4 + 2λ5 + 2λ6 + 2λ7 == 2 +# x_12 + x_23 + x_24 + x_25 + 2λ1 + 2λ2 + 2λ3 + 1λ4 + 1λ5 + 2λ6 + 3λ7 == 2 +# x_13 + x_23 + x_34 + x_35 + 2λ1 + 3λ2 + 2λ3 + 3λ4 + 2λ5 + 3λ6 + 1λ7 == 2 +# x_14 + x_24 + x_34 + x_45 + 2λ1 + 2λ2 + 3λ3 + 3λ4 + 3λ5 + 1λ6 + 1λ7 == 2 +# x_15 + x_25 + x_35 + x_45 + 2λ1 + 1λ2 + 1λ3 + 1λ4 + 2λ5 + 2λ6 + 3λ7 == 2 + +# dw_sp +# min +# 7x_12 + 2x_13 + x_14 + 5x_15 + 3x_23 + 6x_24 + 8x_25 + 4x_34 + 2x_35 + 9x_45 +# s.t. +# x_12 + x_13 + x_14 + x_15 == 1 + +# continuous +# columns +# λ1, λ2, λ3, λ4, λ5, λ6, λ7 + +# integer +# representatives +# x_12, x_13, x_14, x_15, x_23, x_24, x_25, x_34, x_35, x_45 + +# bounds +# λ1 >= 0 +# λ2 >= 0 +# λ3 >= 0 +# λ4 >= 0 +# λ5 >= 0 +# λ6 >= 0 +# λ7 >= 0 +# x_12 >= 0 +# x_13 >= 0 +# x_14 >= 0 +# x_15 >= 0 +# x_23 >= 0 +# x_24 >= 0 +# x_25 >= 0 +# x_34 >= 0 +# x_35 >= 0 +# x_45 >= 0 \ No newline at end of file diff --git a/test/unit/ColGen/colgen_iteration.jl b/test/unit/ColGen/colgen_iteration.jl index deb04d614..d2fca5309 100644 --- a/test/unit/ColGen/colgen_iteration.jl +++ b/test/unit/ColGen/colgen_iteration.jl @@ -9,8 +9,7 @@ # - error handling # - output -# function get_reform_master_and_vars_colgen_iteration() -# form_string1 = """ +# This is the problems that we consider here: # master # min # 7x_12 + 2x_13 + x_14 + 5x_15 + 3x_23 + 6x_24 + 8x_25 + 4x_34 + 2x_35 + 9x_45 + 28λ1 + 25λ2 + 21λ3 + 19λ4 + 22λ5 + 18λ6 + 28λ7 @@ -53,12 +52,6 @@ # x_34 >= 0 # x_35 >= 0 # x_45 >= 0 -# """ - -# _, master, _, _, reform = reformfromstring(form_string1) -# vars_by_name = Dict{String, ClMP.Variable}(ClMP.getname(master, var) => var for (_, var) in ClMP.getvars(master)) -# return reform, master, vars_by_name -# end struct ColGenIterationTestMaster end struct ColGenIterationTestPricing end @@ -133,13 +126,13 @@ ColGen.get_primal_sols(res::ColGenIterationTestPricingResult) = res.primal_sols ColGen.get_dual_bound(res::ColGenIterationTestPricingResult) = res.dual_bound ColGen.compute_sp_init_db(::ColGenIterationTestContext, sp) = -Inf ColGen.set_of_columns(::ColGenIterationTestContext) = Vector{Float64}[] -ColGen.push_in_set!(set, col) = push!(set, col) +ColGen.push_in_set!(set::Vector{Vector{Float64}}, col::Vector) = push!(set, col) ColGen.is_infeasible(res::ColGenIterationTestPricingResult) = res.term_status == ClB.INFEASIBLE ColGen.is_unbounded(res::ColGenIterationTestPricingResult) = res.term_status == ClB.DUAL_INFEASIBLE ColGen.is_optimal(res::ColGenIterationTestPricingResult) = res.term_status == ClB.OPTIMAL ## mock of the pricing solver -function ColGen.optimize_pricing_problem!(ctx::ColGenIterationTestContext, form) +function ColGen.optimize_pricing_problem!(ctx::ColGenIterationTestContext, form, env, master_dual_sol) primal_val = nothing dual_val = nothing sols = Vector{Float64}[] @@ -263,4 +256,3 @@ function colgen_iteration_pricing_unbounded() @test output.unbounded_subproblem == true end register!(unit_tests, "colgen_iteration", colgen_iteration_pricing_unbounded) -