Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
157 changes: 156 additions & 1 deletion src/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,159 @@ function _copy_model(
model::M
) where {M <: JuMP.AbstractModel}
return M()
end
end

function JuMP.copy_extension_data(
data::GDPData{M, V, C, T},
new_model::JuMP.AbstractModel,
old_model::JuMP.AbstractModel
) where {M, V, C, T}
return GDPData{M, V, C}()
end

function copy_gdp_data(
model::M,
new_model::M,
ref_map::GenericReferenceMap
) where {M <: JuMP.AbstractModel}

old_gdp = model.ext[:GDP]
new_gdp = new_model.ext[:GDP]
var_map = Dict(v => ref_map[v] for v in all_variables(model))
lv_map = Dict{LogicalVariableRef{M}, LogicalVariableRef{M}}()
lc_map = Dict{LogicalConstraintRef{M}, LogicalConstraintRef{M}}()
disj_map = Dict{DisjunctionRef{M}, DisjunctionRef{M}}()
disj_con_map = Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}}()
for (idx, var) in old_gdp.logical_variables
old_var_ref = LogicalVariableRef(model, idx)
new_var_data = LogicalVariableData(var.variable, var.name)
new_var = LogicalVariableRef(new_model, idx)
lv_map[old_var_ref] = new_var
new_gdp.logical_variables[idx] = new_var_data
end

for (idx, lc_data) in old_gdp.logical_constraints
old_con_ref = LogicalConstraintRef(model, idx)
new_con_ref = LogicalConstraintRef(new_model, idx)
c = lc_data.constraint
expr = _replace_variables_in_constraint(c.func, lv_map)
new_con = JuMP.build_constraint(error, expr, c.set)
JuMP.add_constraint(new_model, new_con, lc_data.name)
lc_map[old_con_ref] = new_con_ref
end

for (idx, disj_con_data) in old_gdp.disjunct_constraints
old_constraint = disj_con_data.constraint
old_dc_ref = DisjunctConstraintRef(model, idx)
old_indicator = old_gdp.constraint_to_indicator[old_dc_ref]
new_indicator = lv_map[old_indicator]
new_expr = _replace_variables_in_constraint(old_constraint.func,
var_map
)
new_con = JuMP.build_constraint(error, new_expr,
old_constraint.set, Disjunct(new_indicator)
)
new_dc_ref = JuMP.add_constraint(new_model, new_con, disj_con_data.name)
disj_con_map[old_dc_ref] = new_dc_ref
end

for (idx, disj_data) in old_gdp.disjunctions
old_disj = disj_data.constraint
new_indicators = [_replace_variables_in_constraint(indicator, lv_map)
for indicator in old_disj.indicators
]
new_disj = Disjunction(new_indicators, old_disj.nested)
disj_map[DisjunctionRef(model, idx)] = DisjunctionRef(new_model, idx)
new_gdp.disjunctions[idx] = ConstraintData(new_disj, disj_data.name)
end

for (d_ref, lc_ref) in old_gdp.exactly1_constraints
new_lc_ref = lc_map[lc_ref]
new_d_ref = disj_map[d_ref]
new_gdp.exactly1_constraints[new_d_ref] = new_lc_ref
end

for (lv_ref, bref) in old_gdp.indicator_to_binary
new_bref = _remap_indicator_to_binary(bref, var_map)
new_gdp.indicator_to_binary[lv_map[lv_ref]] = new_bref
end

for (lv_ref, con_refs) in old_gdp.indicator_to_constraints
new_lvar_ref = lv_map[lv_ref]
new_con_refs = Vector{Union{DisjunctConstraintRef{M}, DisjunctionRef{M}}}()
for con_ref in con_refs
new_con_ref = _remap_indicator_to_constraint(con_ref,
disj_con_map, disj_map
)
push!(new_con_refs, new_con_ref)
end
new_gdp.indicator_to_constraints[new_lvar_ref] = new_con_refs
end

for (con_ref, lv_ref) in old_gdp.constraint_to_indicator
new_gdp.constraint_to_indicator[
_remap_constraint_to_indicator(con_ref, disj_con_map, disj_map)
] = lv_map[lv_ref]
end

for (v, bounds) in old_gdp.variable_bounds
new_gdp.variable_bounds[var_map[v]] = bounds
end

new_gdp.solution_method = old_gdp.solution_method
new_gdp.ready_to_optimize = old_gdp.ready_to_optimize

return lv_map
end

function copy_gdp_model(model::M) where {M <: JuMP.AbstractModel}
new_model, ref_map = JuMP.copy_model(model)
lv_map = copy_gdp_data(model, new_model, ref_map)
return new_model, ref_map, lv_map
end

function _remap_indicator_to_constraint(
con_ref::DisjunctConstraintRef,
disj_con_map::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
) where {M <: JuMP.AbstractModel}
return disj_con_map[con_ref]
end

function _remap_indicator_to_constraint(
con_ref::DisjunctionRef,
::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
disj_map::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
) where {M <: JuMP.AbstractModel}
return disj_map[con_ref]
end

function _remap_indicator_to_binary(
bref::JuMP.AbstractVariableRef,
var_map::Dict{V, V}
) where {V <: JuMP.AbstractVariableRef}
return var_map[bref]
end

function _remap_indicator_to_binary(
bref::JuMP.GenericAffExpr,
var_map::Dict{V, V}
) where {V <: JuMP.AbstractVariableRef}
return _replace_variables_in_constraint(bref, var_map)
end

function _remap_constraint_to_indicator(
con_ref::DisjunctConstraintRef,
disj_con_map::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
) where {M <: JuMP.AbstractModel}
return disj_con_map[con_ref]
end

function _remap_constraint_to_indicator(
con_ref::DisjunctionRef,
::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
disj_map::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
) where {M <: JuMP.AbstractModel}
return disj_map[con_ref]
end
150 changes: 149 additions & 1 deletion test/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,158 @@ function test_set_optimizer()
@test solver_name(model) == "HiGHS"
end

function test_remapping_functions()
model = GDPModel()
@variable(model, x)
@variable(model, y)
@variable(model, z)

var_map = Dict{VariableRef, VariableRef}(x => y, z => x)

@test DP._remap_indicator_to_binary(x, var_map) == y
@test DP._remap_indicator_to_binary(z, var_map) == x

aff_expr = 2.0 * x + 3.0 * z + 5.0
remapped_expr = DP._remap_indicator_to_binary(aff_expr, var_map)
@test remapped_expr isa JuMP.GenericAffExpr
@test remapped_expr.constant == 5.0
@test remapped_expr.terms[y] == 2.0 # x remapped to y
@test remapped_expr.terms[x] == 3.0 # z remapped to x
@test !haskey(remapped_expr.terms, z) # z shouldn't exist anymore

model2 = GDPModel()
@variable(model2, a[1:3])
@variable(model2, Y[1:3], DP.Logical)

con1 = @constraint(model2, a[1] ≤ 5, DP.Disjunct(Y[1]))
con2 = @constraint(model2, a[2] ≥ 2, DP.Disjunct(Y[2]))
con3 = @constraint(model2, a[3] == 4, DP.Disjunct(Y[3]))

disj1 = DP.@disjunction(model2, Y[1:2])
disj2 = DP.@disjunction(model2, Y[2:3])

disj_con_map = Dict{DisjunctConstraintRef{Model}, DisjunctConstraintRef{Model}}(
con1 => con2,
con2 => con3
)
disj_map = Dict{DisjunctionRef{Model}, DisjunctionRef{Model}}()

@test DP._remap_constraint_to_indicator(con1, disj_con_map, disj_map) == con2
@test DP._remap_constraint_to_indicator(con2, disj_con_map, disj_map) == con3

empty_disj_con_map = Dict{DisjunctConstraintRef{Model}, DisjunctConstraintRef{Model}}()
disj_map2 = Dict{DisjunctionRef{Model}, DisjunctionRef{Model}}(
disj1 => disj2
)

@test DP._remap_constraint_to_indicator(disj1, empty_disj_con_map, disj_map2) == disj2
end

function test_copy_model()
model = DP.GDPModel(HiGHS.Optimizer)
@variable(model, 0 ≤ x[1:2] ≤ 20)
@variable(model, Y[1:2], DP.Logical)
@constraint(model, [i = 1:2], [2,5][i] ≤ x[i] ≤ [6,9][i], DP.Disjunct(Y[1]))
@constraint(model, [i = 1:2], [8,10][i] ≤ x[i] ≤ [11,15][i], DP.Disjunct(Y[2]))
DP.@disjunction(model, Y)
DP._variable_bounds(model)[x[1]] = set_variable_bound_info(x[1], BigM())
DP._variable_bounds(model)[x[2]] = set_variable_bound_info(x[2], BigM())

new_model, ref_map = JuMP.copy_model(model)
@test haskey(new_model.ext, :GDP)
lv_map = DP.copy_gdp_data(model, new_model, ref_map)
@test length(lv_map) == 2
new_model1, ref_map1, lv_map1 = DP.copy_gdp_model(model)
@test haskey(new_model1.ext, :GDP)
@test length(lv_map1) == 2

orig_num_vars = num_variables(model)
orig_num_constrs = num_constraints(model;
count_variable_in_set_constraints = false
)
orig1_num_vars = num_variables(new_model1)
orig1_num_constrs = num_constraints(new_model1;
count_variable_in_set_constraints = false
)

@variable(new_model, z >= 0)
@constraint(new_model, z <= 100)

@test num_variables(model) == orig_num_vars
num_con_m = num_constraints(model;
count_variable_in_set_constraints = false)
num_con_m1 = num_constraints(new_model1;
count_variable_in_set_constraints = false)
@test num_con_m == orig_num_constrs
@test num_con_m1 == orig1_num_constrs
@test !haskey(object_dictionary(model), :z)

@test num_variables(new_model1) == orig1_num_vars

@test !haskey(object_dictionary(new_model1), :z)

@test num_variables(new_model) == orig_num_vars + 1
num_con_m2 = num_constraints(new_model;
count_variable_in_set_constraints = false)
@test num_con_m2 == orig_num_constrs + 1


orig_num_lvars = length(DP._logical_variables(model))
orig_num_disj_cons = length(DP._disjunct_constraints(model))
orig_num_disj = length(DP._disjunctions(model))
orig1_num_lvars = length(DP._logical_variables(new_model1))
orig1_num_disj_cons = length(DP._disjunct_constraints(new_model1))
orig1_num_disj = length(DP._disjunctions(new_model1))

@variable(new_model, W[1:2], DP.Logical)
@constraint(new_model, z >= 5, DP.Disjunct(W[1]))
@constraint(new_model, z <= 3, DP.Disjunct(W[2]))
DP.@disjunction(new_model, W)

@test length(DP._logical_variables(model)) == orig_num_lvars
@test length(DP._disjunct_constraints(model)) == orig_num_disj_cons
@test length(DP._disjunctions(model)) == orig_num_disj
@test !haskey(object_dictionary(model), :W)

# Store original reformulation methods
orig_methods = Dict(
:model => [DP._solution_method(model)],
:new_model1 => [DP._solution_method(new_model1)]
)

# Solve new_model with Big-M reformulation
set_optimizer(new_model, HiGHS.Optimizer)
set_silent(new_model)
DP.optimize!(new_model, gdp_method = BigM())
@test termination_status(new_model) in (MOI.OPTIMAL, MOI.LOCALLY_SOLVED)

# Verify reformulation methods of other models are unchanged
@test DP._solution_method(model) == orig_methods[:model][]
@test DP._solution_method(new_model1) == orig_methods[:new_model1][]

# Verify model and new_model1 were not modified by the solve
@test num_variables(model) == orig_num_vars
@test num_variables(new_model1) == orig1_num_vars
@test length(DP._logical_variables(model)) == orig_num_lvars
@test length(DP._logical_variables(new_model1)) == orig1_num_lvars
@test length(DP._disjunct_constraints(model)) == orig_num_disj_cons
@test length(DP._disjunct_constraints(new_model1)) == orig1_num_disj_cons
@test length(DP._disjunctions(model)) == orig_num_disj
@test length(DP._disjunctions(new_model1)) == orig1_num_disj

@test !haskey(object_dictionary(new_model1), :W)

@test length(DP._logical_variables(new_model)) == orig_num_lvars + 2
@test length(DP._disjunct_constraints(new_model)) == orig_num_disj_cons + 2
@test length(DP._disjunctions(new_model)) == orig_num_disj + 1
end

@testset "GDP Model" begin
test_GDPData()
test_empty_model()
test_non_gdp_model()
test_copy_model()
test_creation_optimizer()
test_set_optimizer()
end
test_remapping_functions()
end
14 changes: 14 additions & 0 deletions test/solve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ function test_linear_gdp_example(m, use_complements = false)
@test value(Y[2])
@test !value(W[1])
@test !value(W[2])

m_copy, ref_map = JuMP.copy_model(m)
lv_map = DP.copy_gdp_data(m, m_copy, ref_map)
set_optimizer(m_copy, HiGHS.Optimizer)
set_optimizer_attribute(m_copy, "output_flag", false)
optimize!(m_copy, gdp_method = BigM())
@test termination_status(m_copy) == MOI.OPTIMAL
@test objective_value(m_copy) ≈ 11
@test value.(x) == value.(ref_map[x])
@test value.(Y[1]) == value.(lv_map[Y[1]])
@test value.(Y[2]) == value.(lv_map[Y[2]])
@test !value(W[1])
@test !value(W[2])
Comment thread
dnguyen227 marked this conversation as resolved.

end

function test_quadratic_gdp_example(use_complements = false) #psplit does not work with complements
Expand Down
Loading