Skip to content

Commit c36dfef

Browse files
authored
Merge branch 'master' into loa_two_models
2 parents 5317552 + 48a1ac5 commit c36dfef

3 files changed

Lines changed: 153 additions & 1 deletion

File tree

Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DisjunctiveProgramming"
22
uuid = "0d27d021-0159-4c7d-b4a7-9ccb5d9366cf"
33
authors = ["hdavid16 <hperez16@gmail.com>"]
4-
version = "0.6.0"
4+
version = "0.6.1"
55

66
[deps]
77
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
@@ -21,6 +21,9 @@ JuMP = "1.18"
2121
Juniper = "0.9.3"
2222
Reexport = "1"
2323
julia = "1.10"
24+
Juniper = "0.9.3"
25+
Ipopt = "1.9.0"
26+
InfiniteOpt = "0.6.3"
2427

2528
[extras]
2629
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"

ext/InfiniteDisjunctiveProgramming.jl

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,4 +1056,83 @@ _add_to_transcribed_dict(
10561056
) =
10571057
(d[ts] = values[1]; nothing)
10581058

1059+
################################################################################
1060+
1061+
# Build CP subproblem: reformulate the InfiniteModel in-place, transcribe,
1062+
# copy, and wrap in GDPSubmodel with forward variable map.
1063+
function DP.copy_and_reformulate(
1064+
model::InfiniteOpt.InfiniteModel,
1065+
decision_vars::Vector{InfiniteOpt.GeneralVariableRef},
1066+
reform_method::DP.AbstractReformulationMethod,
1067+
method::DP.CuttingPlanes
1068+
)
1069+
DP.reformulate_model(model, reform_method)
1070+
InfiniteOpt.build_transformation_backend!(model)
1071+
transcribed = InfiniteOpt.transformation_model(model)
1072+
transcription_fwd = Dict{InfiniteOpt.GeneralVariableRef,
1073+
Vector{JuMP.VariableRef}}()
1074+
for v in DP.collect_all_vars(model)
1075+
transcription_var = InfiniteOpt.transformation_variable(v)
1076+
var_prefs = InfiniteOpt.parameter_refs(v)
1077+
transcription_fwd[v] = isempty(var_prefs) ?
1078+
[transcription_var] : vec(transcription_var)
1079+
end
1080+
sub_copy, copy_map = JuMP.copy_model(transcribed)
1081+
fwd_map = Dict{InfiniteOpt.GeneralVariableRef, Vector{JuMP.VariableRef}}()
1082+
for v in decision_vars
1083+
haskey(transcription_fwd, v) || continue
1084+
fwd_map[v] = [copy_map[transcribed_var] for transcribed_var in transcription_fwd[v]]
1085+
end
1086+
sub = DP.GDPSubmodel(sub_copy, decision_vars, fwd_map)
1087+
JuMP.set_optimizer(sub.model, method.optimizer)
1088+
JuMP.set_silent(sub.model)
1089+
return sub
1090+
end
1091+
1092+
# Read per-support values from the transformation backend.
1093+
function DP.extract_solution(model::InfiniteOpt.InfiniteModel)
1094+
dvars = DP.collect_cutting_planes_vars(model)
1095+
V = eltype(dvars)
1096+
T = JuMP.value_type(typeof(model))
1097+
sol = Dict{V, Vector{T}}()
1098+
for v in dvars
1099+
transcription_var = InfiniteOpt.transformation_variable(v)
1100+
var_prefs = InfiniteOpt.parameter_refs(v)
1101+
sol[v] = isempty(var_prefs) ? [JuMP.value(transcription_var)] :
1102+
JuMP.value.(vec(transcription_var))
1103+
end
1104+
return sol
1105+
end
1106+
1107+
# Add a pointwise-sum cut directly to the transformation backend and mark
1108+
# it ready so the next optimize! doesn't re-transcribe and wipe the cut.
1109+
function DP.add_cut(
1110+
model::InfiniteOpt.InfiniteModel,
1111+
decision_vars::Vector{InfiniteOpt.GeneralVariableRef},
1112+
rBM_sol::Dict{<:JuMP.AbstractVariableRef, <:Vector{<:Number}},
1113+
sep_sol::Dict{<:JuMP.AbstractVariableRef, <:Vector{<:Number}}
1114+
)
1115+
transcribed = InfiniteOpt.transformation_model(model)
1116+
cut_expr = zero(JuMP.GenericAffExpr{
1117+
JuMP.value_type(typeof(transcribed)),
1118+
JuMP.variable_ref_type(transcribed)})
1119+
for var in decision_vars
1120+
haskey(rBM_sol, var) || continue
1121+
haskey(sep_sol, var) || continue
1122+
rbm_vals = rBM_sol[var]
1123+
sep_vals = sep_sol[var]
1124+
transcription_var = InfiniteOpt.transformation_variable(var)
1125+
transcribed_vars = transcription_var isa AbstractArray ?
1126+
vec(transcription_var) : [transcription_var]
1127+
for k in eachindex(transcribed_vars)
1128+
xi = 2 * (sep_vals[k] - rbm_vals[k])
1129+
JuMP.add_to_expression!(cut_expr, xi, transcribed_vars[k])
1130+
JuMP.add_to_expression!(cut_expr, -xi * sep_vals[k])
1131+
end
1132+
end
1133+
JuMP.@constraint(transcribed, cut_expr >= 0)
1134+
InfiniteOpt.set_transformation_backend_ready(model, true)
1135+
return
1136+
end
1137+
10591138
end

test/extensions/InfiniteDisjunctiveProgramming.jl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,76 @@ function test_mbm_finite_and_integer_var()
513513
[MOI.OPTIMAL, MOI.LOCALLY_SOLVED]
514514
end
515515

516+
function test_mbm_infinite_simple()
517+
model = InfiniteGDPModel(HiGHS.Optimizer)
518+
set_silent(model)
519+
model = InfiniteGDPModel(HiGHS.Optimizer)
520+
set_silent(model)
521+
K = 4
522+
@infinite_parameter(model, t [0, 1], num_supports = K)
523+
@variable(model, 0 <= x <= 10, Infinite(t))
524+
@variable(model, Y[1:2], InfiniteLogical(t))
525+
@constraint(model, x >= 5, Disjunct(Y[1]))
526+
@constraint(model, x <= 3, Disjunct(Y[2]))
527+
@disjunction(model, Y)
528+
JuMP.fix(Y[2], true) # force disj 2 active
529+
@objective(model, Min, (x, t))
530+
DP.reformulate_model(model, BigM(10.0))
531+
set_optimizer(model, HiGHS.Optimizer)
532+
set_silent(model)
533+
optimize!(model, ignore_optimize_hook = true)
534+
sol = DP.extract_solution(model)
535+
@test haskey(sol, x)
536+
@test length(sol[x]) == K
537+
@test all(v -> isapprox(v, 0.0; atol=1e-6), sol[x])
538+
end
539+
540+
# add_cut adds one pointwise-sum cut to the transformation backend and
541+
# marks the backend ready so the next optimize! does NOT re-transcribe.
542+
function test_add_cut_infinite()
543+
model = InfiniteGDPModel(HiGHS.Optimizer)
544+
set_silent(model)
545+
K = 3
546+
@infinite_parameter(model, t [0, 1], num_supports = K)
547+
@variable(model, 0 <= x <= 10, Infinite(t))
548+
@variable(model, Y[1:2], InfiniteLogical(t))
549+
@constraint(model, x >= 5, Disjunct(Y[1]))
550+
@constraint(model, x <= 3, Disjunct(Y[2]))
551+
@disjunction(model, Y)
552+
DP.reformulate_model(model, BigM(10.0))
553+
InfiniteOpt.build_transformation_backend!(model)
554+
transcribed = InfiniteOpt.transformation_model(model)
555+
n_before = JuMP.num_constraints(transcribed;
556+
count_variable_in_set_constraints = false)
557+
rBM_sol = Dict(x => [1.0, 2.0, 3.0])
558+
sep_sol = Dict(x => [0.5, 1.5, 2.5])
559+
DP.add_cut(model, [x], rBM_sol, sep_sol)
560+
n_after = JuMP.num_constraints(transcribed;
561+
count_variable_in_set_constraints = false)
562+
@test n_after == n_before + 1
563+
# set_transformation_backend_ready(true) — next optimize! should
564+
# reuse without re-transcribing (otherwise our cut would be lost)
565+
@test InfiniteOpt.transformation_backend_ready(model)
566+
end
567+
568+
# MBM with finite + integer variables in an InfiniteModel.
569+
function test_mbm_finite_and_integer_var()
570+
model = InfiniteGDPModel(HiGHS.Optimizer)
571+
set_silent(model)
572+
@infinite_parameter(model, t [0, 1], num_supports = 10)
573+
@variable(model, 0 <= x <= 10, Infinite(t))
574+
@variable(model, 0 <= w <= 5, Int)
575+
@variable(model, Y[1:2], InfiniteLogical(t))
576+
@constraint(model, x + w >= 5, Disjunct(Y[1]))
577+
@constraint(model, x + w <= 3, Disjunct(Y[2]))
578+
@disjunction(model, Y)
579+
@objective(model, Min, (x, t) + w)
580+
@test optimize!(model,
581+
gdp_method = MBM(HiGHS.Optimizer)) isa Nothing
582+
@test termination_status(model) in
583+
[MOI.OPTIMAL, MOI.LOCALLY_SOLVED]
584+
end
585+
516586
function test_mbm_infinite_simple()
517587
model = InfiniteGDPModel(HiGHS.Optimizer)
518588
set_silent(model)

0 commit comments

Comments
 (0)