Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix isinteger + continuous columns if sp has only continuous vars #551

Merged
merged 2 commits into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/Algorithm/colgen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,19 +235,15 @@ set_bestcol_id!(spinfo::SubprobInfo, varid::VarId) = spinfo.bestcol_id = varid
function insert_cols_in_master!(
masterform::Formulation, spinfo::SubprobInfo, phase::Int64, spform::Formulation,
)
sp_uid = getuid(spform)
nb_of_gen_col = 0

for sol_id in spinfo.recorded_sol_ids
nb_of_gen_col += 1
name = string("MC_", getsortuid(sol_id))
lb = 0.0
ub = Inf
kind = Continuous
duty = MasterCol
mc = setcol_from_sp_primalsol!(
masterform, spform, sol_id, name, duty; lb=lb, ub=ub, kind=kind
)
mc = setcol_from_sp_primalsol!(masterform, spform, sol_id, name, duty; lb=lb, ub=ub)
if phase == 1
setcurcost!(masterform, mc, 0.0)
end
Expand Down
9 changes: 5 additions & 4 deletions src/MathProg/duties.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mutable struct DwSp <: AbstractSpDuty
setup_var::Union{VarId, Nothing}
lower_multiplicity::Int
upper_multiplicity::Int
column_var_kind::VarKind
end

"A Benders subproblem of a formulation decomposed using Benders."
Expand Down Expand Up @@ -47,14 +48,14 @@ struct BendersSp <: AbstractSpDuty end
AbstractDwSpVar <= Duty{Variable}
DwSpPricingVar <= AbstractDwSpVar
DwSpSetupVar <= AbstractDwSpVar
DwSpPureVar <= AbstractDwSpVar
#DwSpPureVar <= AbstractDwSpVar
DwSpPrimalSol <= AbstractDwSpVar
AbstractBendSpVar <= Duty{Variable}
AbstractBendSpSlackMastVar <= AbstractBendSpVar
BendSpSlackFirstStageVar <= AbstractBendSpSlackMastVar
BendSpSlackSecondStageCostVar <= AbstractBendSpSlackMastVar
BendSpSepVar <= AbstractBendSpVar
BendSpPureVar <= AbstractBendSpVar
#BendSpPureVar <= AbstractBendSpVar
BendSpPrimalSol <= AbstractBendSpVar
end

Expand Down Expand Up @@ -108,11 +109,11 @@ function isaStaticDuty(duty::NestedEnum)
duty <= MasterRepPricingSetupVar ||
duty <= DwSpPricingVar ||
duty <= DwSpSetupVar ||
duty <= DwSpPureVar ||
#duty <= DwSpPureVar ||
duty <= DwSpPrimalSol ||
duty <= DwSpDualSol ||
duty <= BendSpSepVar ||
duty <= BendSpPureVar ||
#duty <= BendSpPureVar ||
duty <= BendSpSlackFirstStageVar ||
duty <= BendSpSlackSecondStageCostVar ||
duty <= OriginalConstr ||
Expand Down
48 changes: 29 additions & 19 deletions src/MathProg/formulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ end

function setcol_from_sp_primalsol!(
masterform::Formulation, spform::Formulation, sol_id::VarId, name::String,
duty::Duty{Variable}; lb::Float64 = 0.0, ub::Float64 = Inf, kind::VarKind = Continuous,
duty::Duty{Variable}; lb::Float64 = 0.0, ub::Float64 = Inf,
inc_val::Float64 = 0.0, is_active::Bool = true, is_explicit::Bool = true,
moi_index::MoiVarIndex = MoiVarIndex(), custom_data::Union{Nothing, AbstractCustomData} = nothing
)
Expand All @@ -322,7 +322,7 @@ function setcol_from_sp_primalsol!(
cost = cost,
lb = lb,
ub = ub,
kind = kind,
kind = spform.duty_data.column_var_kind,
inc_val = inc_val,
is_active = is_active,
is_explicit = is_explicit,
Expand All @@ -331,6 +331,9 @@ function setcol_from_sp_primalsol!(
id = sol_id,
custom_data = get(spform.manager.primal_sols_custom_data, sol_id, nothing)
)

setcurkind!(masterform, mast_col, Continuous)

return mast_col
end

Expand Down Expand Up @@ -481,29 +484,36 @@ function _addlocalartvar!(form::Formulation, constr::Constraint, abs_cost::Float
return
end

"""
enforce_integrality!(formulation)

Set the current kind of each active & explicit variable of the formulation to its perennial kind.
"""
function enforce_integrality!(form::Formulation)
@logmsg LogLevel(-1) string("Enforcing integrality of formulation ", getuid(form))
for (varid, var) in getvars(form)
!iscuractive(form, varid) && continue
!isexplicit(form, varid) && continue
getcurkind(form, varid) == Integ && continue
getcurkind(form, varid) == Binary && continue
if getduty(varid) <= MasterCol || getperenkind(form, varid) != Continuous
@logmsg LogLevel(-3) string("Setting kind of var ", getname(form, var), " to Integer")
setcurkind!(form, varid, Integ)
for (_, var) in getvars(form)
enforce = iscuractive(form, var) && isexplicit(form, var)
enforce &= getcurkind(form, var) === Continuous
enforce &= getperenkind(form, var) !== Continuous
if enforce
setcurkind!(form, var, getperenkind(form, var))
end
end
return
end

function relax_integrality!(form::Formulation) # TODO remove : should be in Algorithm
@logmsg LogLevel(-1) string("Relaxing integrality of formulation ", getuid(form))
for (varid, var) in getvars(form)
!iscuractive(form, varid) && continue
!isexplicit(form, varid) && continue
getcurkind(form, var) == Continuous && continue
@logmsg LogLevel(-3) string("Setting kind of var ", getname(form, var), " to continuous")
setcurkind!(form, varid, Continuous)
"""
relax_integrality!(formulation)

Set the current kind of each active & explicit integer or binary variable of the formulation
to continuous.
"""
function relax_integrality!(form::Formulation)
for (_, var) in getvars(form)
relax = iscuractive(form, var) && isexplicit(form, var)
relax &= getcurkind(form, var) !== Continuous
if relax
setcurkind!(form, var, Continuous)
end
end
return
end
Expand Down
8 changes: 3 additions & 5 deletions src/MathProg/solutions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ end

function Base.isinteger(sol::Solution)
for (vc_id, val) in sol
#if getperenkind(sol.model, vc_id) != Continuous
abs(round(val) - val) <= 1e-5 || return false
#end
if getperenkind(sol.model, vc_id) !== Continuous && abs(round(val) - val) > 1e-5
return false
end
end
return true
end

isfractional(sol::Solution) = !Base.isinteger(sol)

function contains(sol::PrimalSolution, f::Function)
for (varid, val) in sol
f(varid) && return true
Expand Down
23 changes: 19 additions & 4 deletions src/decomposition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function instantiatesp!(
)
spform = create_formulation!(
env,
MathProg.DwSp(nothing, 1, 1);
MathProg.DwSp(nothing, 1, 1, Integ);
parent_formulation = masterform,
obj_sense = getobjsense(masterform)
)
Expand Down Expand Up @@ -138,7 +138,9 @@ function create_side_vars_constrs!(
@assert length(setupvars) == 1
setupvar = collect(values(setupvars))[1]
setuprepvar = clonevar!(origform, masterform, spform, setupvar, MasterRepPricingSetupVar, is_explicit = false)
# create convexity constraint

# create convexity constraint & storing information about the convexity constraint
# in the duty data of the formulation
lb_mult = Float64(BD.getlowermultiplicity(ann))
name = string("sp_lb_", spuid)
lb_conv_constr = setconstr!(
Expand All @@ -156,11 +158,24 @@ function create_side_vars_constrs!(
kind = Essential, sense = Less, inc_val = 100.0,
loc_art_var_abs_cost = env.params.local_art_var_cost
)
masterform.parent_formulation.dw_pricing_sp_ub[spuid] = getid(ub_conv_constr)
coefmatrix[getid(ub_conv_constr), getid(setuprepvar)] = 1.0

spform.duty_data.lower_multiplicity = lb_mult
spform.duty_data.upper_multiplicity = ub_mult
spform.duty_data.setup_var = getid(setupvar)
masterform.parent_formulation.dw_pricing_sp_ub[spuid] = getid(ub_conv_constr)
coefmatrix[getid(ub_conv_constr), getid(setuprepvar)] = 1.0

# If pricing subproblem variables are continuous, the master columns generated by
# the subproblem must have a continuous perenkind.
# This piece of information is stored in the duty data of the formulation.
continuous_columns = true
for (varid, var) in getvars(spform)
if getduty(varid) <= DwSpPricingVar && getperenkind(spform, var) !== Continuous
continuous_columns = false
break
end
end
spform.duty_data.column_var_kind = continuous_columns ? Continuous : Integ
end
return
end
Expand Down
76 changes: 76 additions & 0 deletions test/issues_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,78 @@ function branching_file_completion()
@test JuMP.termination_status(model) == MOI.OPTIMAL
end

# Issue https://github.com/atoptima/Coluna.jl/issues/550
function continuous_vars_in_sp()
# Simple min cost flow problem
#
# n1 ------ f[1] -------> n3
# \ ^
# \ /
# -f[2]-> n2 --f[3]--
#
# n1: demand = -10.1
# n2: demand = 0
# n3: demand = 10.1
# f[1]: cost = 0, capacity = 8.5, in mip model only integer flow allowed
# f[2]: cost = 50, (capacity = 5 can be activated by removing comment at constraint, line 93)
# f[3]: cost = 50
#
# Correct solution for non-integer f[1]
# f[1] = 8.5, f[2] = f[3] = 1.6, cost = 8.5*0 + 1.6*2*50 = 160
# Correct solution for integer f[1]
# f[1] = 8, f[2] = f[3] = 2.1, cost = 8.5*0 + 2.1*2*50 = 210
#
function solve_flow_model(f1_integer, coluna)
@axis(M, 1:1)
model = BlockDecomposition.BlockModel(coluna, direct_model=true)
@variable(model, f[1:3, m in M])
if f1_integer
JuMP.set_integer(f[1, 1])
end
@constraint(model, n1[m in M], f[1,m] + f[2,m] == 10.1)
@constraint(model, n2[m in M], f[2,m] == f[3,m])
@constraint(model, n3[m in M], f[1,m] + f[3,m] == 10.1)
@constraint(model, cap1, sum(f[1,m] for m in M) <= 8.5)
#@JuMP.constraint(model, cap2, sum(f[2,m] for m in M) <= 5)
@objective(model, Min, 50 * f[2,1] + 50 * f[3,1])

@dantzig_wolfe_decomposition(model, decomposition, M)

subproblems = BlockDecomposition.getsubproblems(decomposition)
BlockDecomposition.specify!.(subproblems, lower_multiplicity=1, upper_multiplicity=1)

optimize!(model)

if f1_integer
@test termination_status(model) == MOI.OPTIMAL
@test primal_status(model) == MOI.FEASIBLE_POINT
@test objective_value(model) ≈ 210
@test value(f[1,1]) ≈ 8
@test value(f[2,1]) ≈ 2.1
@test value(f[3,1]) ≈ 2.1
else
@test termination_status(model) == MOI.OPTIMAL
@test primal_status(model) == MOI.FEASIBLE_POINT
@test objective_value(model) ≈ 160
@test value(f[1,1]) ≈ 8.5
@test value(f[2,1]) ≈ 1.6
@test value(f[3,1]) ≈ 1.6
end
end

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => Coluna.Params(
solver=Coluna.Algorithm.TreeSearchAlgorithm(),
),
"default_optimizer" => GLPK.Optimizer
);

solve_flow_model(false, coluna)
solve_flow_model(true, coluna)
return
end

function test_issues_fixed()
@testset "no_decomposition" begin
solve_with_no_decomposition()
Expand Down Expand Up @@ -262,6 +334,10 @@ function test_issues_fixed()
@testset "branching_file_completion" begin
branching_file_completion()
end

@testset "continuous_vars_in_sp" begin
continuous_vars_in_sp()
end
end

test_issues_fixed()