@@ -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