Skip to content

Commit 5709764

Browse files
authored
Addition of copy_model and copy_gdp_data to copy GDP models. (#126)
* . * . * working version with tests * Refactor variable handling and add model copying function Refactor variable creation and add copy_model_and_gdp_data function. * Add tests for copy_model_and_gdp_data function * added remapping functions for specific gdpdata field. created more tests accordingly. addition of test to check that model instances do not affect each other. * added solving test in modeljl for copy_model * additional comments
1 parent aba6f8b commit 5709764

File tree

3 files changed

+432
-2
lines changed

3 files changed

+432
-2
lines changed

src/utilities.jl

Lines changed: 269 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,272 @@ function _copy_model(
66
model::M
77
) where {M <: JuMP.AbstractModel}
88
return M()
9-
end
9+
end
10+
11+
"""
12+
JuMP.copy_extension_data(
13+
data::GDPData,
14+
new_model::JuMP.AbstractModel,
15+
old_model::JuMP.AbstractModel
16+
)::GDPData
17+
18+
Extend `JuMP.copy_extension_data` to initialize an empty [`GDPData`](@ref) object
19+
for the copied model. This is the first step in the model copying process and is
20+
automatically called by `JuMP.copy_model`. The actual GDP data (logical variables,
21+
disjunctions, etc.) is copied separately via [`copy_gdp_data`](@ref).
22+
"""
23+
function JuMP.copy_extension_data(
24+
data::GDPData{M, V, C, T},
25+
new_model::JuMP.AbstractModel,
26+
old_model::JuMP.AbstractModel
27+
) where {M, V, C, T}
28+
return GDPData{M, V, C}()
29+
end
30+
31+
"""
32+
copy_gdp_data(
33+
model::JuMP.AbstractModel,
34+
new_model::JuMP.AbstractModel,
35+
ref_map::JuMP.GenericReferenceMap
36+
)::Dict{LogicalVariableRef, LogicalVariableRef}
37+
38+
Copy all GDP-specific data from `model` to `new_model`, including logical variables,
39+
logical constraints, disjunct constraints, and disjunctions. This function is called
40+
automatically by [`copy_gdp_model`](@ref) after `JuMP.copy_model` has copied the base
41+
model structure.
42+
43+
**Arguments**
44+
- `model::JuMP.AbstractModel`: The source model containing GDP data to copy.
45+
- `new_model::JuMP.AbstractModel`: The destination model that will receive the copied GDP data.
46+
- `ref_map::JuMP.GenericReferenceMap`: The reference map from `JuMP.copy_model` that maps
47+
old variable references to new ones.
48+
49+
**Returns**
50+
- `Dict{LogicalVariableRef, LogicalVariableRef}`: A mapping from old logical variable
51+
references to new logical variable references.
52+
"""
53+
function copy_gdp_data(
54+
model::M,
55+
new_model::M,
56+
ref_map::GenericReferenceMap
57+
) where {M <: JuMP.AbstractModel}
58+
59+
old_gdp = model.ext[:GDP]
60+
61+
# GDPData contains the following fields.
62+
# DICTIONARIES (for loops below)
63+
# - logical_variables
64+
# - logical_constraints
65+
# - disjunct_constraints
66+
# - disjunctions
67+
# - exactly1_constraints
68+
# - indicator_to_binary
69+
# - indicator_to_constraints
70+
# - constraint_to_indicator
71+
# - variable_bounds
72+
# SINGLE VALUES (copy directly)
73+
# - solution_method
74+
# - ready_to_optimize
75+
76+
new_gdp = new_model.ext[:GDP]
77+
78+
# Creating maps from old to new model.
79+
var_map = Dict(v => ref_map[v] for v in all_variables(model))
80+
lv_map = Dict{LogicalVariableRef{M}, LogicalVariableRef{M}}()
81+
lc_map = Dict{LogicalConstraintRef{M}, LogicalConstraintRef{M}}()
82+
disj_map = Dict{DisjunctionRef{M}, DisjunctionRef{M}}()
83+
disj_con_map = Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}}()
84+
85+
# Copying logical variables
86+
for (idx, var) in old_gdp.logical_variables
87+
old_var_ref = LogicalVariableRef(model, idx)
88+
new_var_data = LogicalVariableData(var.variable, var.name)
89+
new_var = LogicalVariableRef(new_model, idx)
90+
lv_map[old_var_ref] = new_var
91+
# Update to new_gdp.logical_variables
92+
new_gdp.logical_variables[idx] = new_var_data
93+
end
94+
95+
# Copying logical constraints
96+
for (idx, lc_data) in old_gdp.logical_constraints
97+
old_con_ref = LogicalConstraintRef(model, idx)
98+
new_con_ref = LogicalConstraintRef(new_model, idx)
99+
c = lc_data.constraint
100+
expr = _replace_variables_in_constraint(c.func, lv_map)
101+
new_con = JuMP.build_constraint(error, expr, c.set)
102+
JuMP.add_constraint(new_model, new_con, lc_data.name)
103+
lc_map[old_con_ref] = new_con_ref
104+
end
105+
106+
# Copying disjunct constraints
107+
for (idx, disj_con_data) in old_gdp.disjunct_constraints
108+
old_constraint = disj_con_data.constraint
109+
old_dc_ref = DisjunctConstraintRef(model, idx)
110+
old_indicator = old_gdp.constraint_to_indicator[old_dc_ref]
111+
new_indicator = lv_map[old_indicator]
112+
new_expr = _replace_variables_in_constraint(old_constraint.func,
113+
var_map
114+
)
115+
# Update to new_gdp.disjunct_constraints
116+
new_con = JuMP.build_constraint(error, new_expr,
117+
old_constraint.set, Disjunct(new_indicator)
118+
)
119+
new_dc_ref = JuMP.add_constraint(new_model, new_con, disj_con_data.name)
120+
disj_con_map[old_dc_ref] = new_dc_ref
121+
end
122+
123+
# Copying disjunctions
124+
for (idx, disj_data) in old_gdp.disjunctions
125+
old_disj = disj_data.constraint
126+
new_indicators = [_replace_variables_in_constraint(indicator, lv_map)
127+
for indicator in old_disj.indicators
128+
]
129+
new_disj = Disjunction(new_indicators, old_disj.nested)
130+
disj_map[DisjunctionRef(model, idx)] = DisjunctionRef(new_model, idx)
131+
# Update to new_gdp.disjunctions
132+
new_gdp.disjunctions[idx] = ConstraintData(new_disj, disj_data.name)
133+
end
134+
135+
# Copying exactly1 constraints
136+
for (d_ref, lc_ref) in old_gdp.exactly1_constraints
137+
new_lc_ref = lc_map[lc_ref]
138+
new_d_ref = disj_map[d_ref]
139+
# Update to new_gdp.exactly1_constraints
140+
new_gdp.exactly1_constraints[new_d_ref] = new_lc_ref
141+
end
142+
143+
# Copying indicator to binary
144+
for (lv_ref, bref) in old_gdp.indicator_to_binary
145+
new_bref = _remap_indicator_to_binary(bref, var_map)
146+
# Update to new_gdp.indicator_to_binary
147+
new_gdp.indicator_to_binary[lv_map[lv_ref]] = new_bref
148+
end
149+
150+
# Copying indicator to constraints
151+
for (lv_ref, con_refs) in old_gdp.indicator_to_constraints
152+
new_lvar_ref = lv_map[lv_ref]
153+
new_con_refs = Vector{Union{DisjunctConstraintRef{M}, DisjunctionRef{M}}}()
154+
for con_ref in con_refs
155+
new_con_ref = _remap_indicator_to_constraint(con_ref,
156+
disj_con_map, disj_map
157+
)
158+
push!(new_con_refs, new_con_ref)
159+
end
160+
# Update to new_gdp.indicator_to_constraints
161+
new_gdp.indicator_to_constraints[new_lvar_ref] = new_con_refs
162+
end
163+
164+
# Copying constraint to indicator
165+
for (con_ref, lv_ref) in old_gdp.constraint_to_indicator
166+
# Update to new_gdp.constraint_to_indicator
167+
new_gdp.constraint_to_indicator[
168+
_remap_constraint_to_indicator(con_ref, disj_con_map, disj_map)
169+
] = lv_map[lv_ref]
170+
end
171+
172+
# Copying variable bounds
173+
for (v, bounds) in old_gdp.variable_bounds
174+
# Update to new_gdp.variable_bounds
175+
new_gdp.variable_bounds[var_map[v]] = bounds
176+
end
177+
178+
# Copying solution method and ready to optimize
179+
new_gdp.solution_method = old_gdp.solution_method
180+
new_gdp.ready_to_optimize = old_gdp.ready_to_optimize
181+
182+
return lv_map
183+
end
184+
185+
"""
186+
copy_gdp_model(model::JuMP.AbstractModel)
187+
188+
Create a copy of a [`GDPModel`](@ref), including all variables, constraints, and
189+
GDP-specific data (logical variables, disjunctions, etc.).
190+
191+
**Arguments**
192+
- `model::JuMP.AbstractModel`: The GDP model to copy.
193+
194+
**Returns**
195+
A tuple `(new_model, ref_map, lv_map)` where:
196+
- `new_model`: The copied model.
197+
- `ref_map::JuMP.GenericReferenceMap`: Maps old variable and constraint references to new ones.
198+
- `lv_map::Dict{LogicalVariableRef, LogicalVariableRef}`: Maps old logical variable
199+
references to new ones.
200+
201+
## Example
202+
```julia
203+
using DisjunctiveProgramming, HiGHS
204+
model = GDPModel(HiGHS.Optimizer)
205+
@variable(model, x)
206+
@variable(model, Y[1:2], LogicalVariable)
207+
@constraint(model, x <= 10, Disjunct(Y[1]))
208+
@constraint(model, x >= 20, Disjunct(Y[2]))
209+
@disjunction(model, Y)
210+
211+
new_model, ref_map, lv_map = copy_gdp_model(model)
212+
```
213+
"""
214+
function copy_gdp_model(model::M) where {M <: JuMP.AbstractModel}
215+
new_model, ref_map = JuMP.copy_model(model)
216+
lv_map = copy_gdp_data(model, new_model, ref_map)
217+
return new_model, ref_map, lv_map
218+
end
219+
################################################################################
220+
# GDP REMAPPING
221+
################################################################################
222+
# These remapping functions use multiple dispatch to handle different types that
223+
# can appear in GDP data structures during model copying.
224+
#
225+
# Indicators can be represented by a variable or an affine expression to
226+
# indicate a complementary relationship with another variable.
227+
# This translates to a binary or affine expression in its binary reformulation.
228+
#
229+
# Depending on the above, different mappings are required for indicator_to_binary,
230+
# indicator_to_constraints, and constraint_to_indicator.
231+
################################################################################
232+
233+
function _remap_indicator_to_constraint(
234+
con_ref::DisjunctConstraintRef,
235+
disj_con_map::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
236+
::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
237+
) where {M <: JuMP.AbstractModel}
238+
return disj_con_map[con_ref]
239+
end
240+
241+
function _remap_indicator_to_constraint(
242+
con_ref::DisjunctionRef,
243+
::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
244+
disj_map::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
245+
) where {M <: JuMP.AbstractModel}
246+
return disj_map[con_ref]
247+
end
248+
249+
function _remap_indicator_to_binary(
250+
bref::JuMP.AbstractVariableRef,
251+
var_map::Dict{V, V}
252+
) where {V <: JuMP.AbstractVariableRef}
253+
return var_map[bref]
254+
end
255+
256+
function _remap_indicator_to_binary(
257+
bref::JuMP.GenericAffExpr,
258+
var_map::Dict{V, V}
259+
) where {V <: JuMP.AbstractVariableRef}
260+
return _replace_variables_in_constraint(bref, var_map)
261+
end
262+
263+
function _remap_constraint_to_indicator(
264+
con_ref::DisjunctConstraintRef,
265+
disj_con_map::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
266+
::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
267+
) where {M <: JuMP.AbstractModel}
268+
return disj_con_map[con_ref]
269+
end
270+
271+
function _remap_constraint_to_indicator(
272+
con_ref::DisjunctionRef,
273+
::Dict{DisjunctConstraintRef{M}, DisjunctConstraintRef{M}},
274+
disj_map::Dict{DisjunctionRef{M}, DisjunctionRef{M}}
275+
) where {M <: JuMP.AbstractModel}
276+
return disj_map[con_ref]
277+
end

0 commit comments

Comments
 (0)