1818from pyomo .common .modeling import unique_component_name
1919
2020from pyomo .core import (
21- Binary , Block , BooleanVar , Connector , Constraint , Expression ,
21+ Any , Binary , Block , BooleanVar , Connector , Constraint , Expression ,
2222 ExternalFunction , maximize , minimize , NonNegativeIntegers , Objective ,
2323 Param , RangeSet , Set , SetOf , SortComponents , Suffix , value , Var
2424)
@@ -105,7 +105,9 @@ class MultipleBigMTransformation(Transformation):
105105
106106 Note: Unlike in the bigm transformation, we require the keys in this
107107 mapping specify the components the M value applies to exactly in order
108- to avoid ambiguity.
108+ to avoid ambiguity. However, if the 'only_mbigm_bound_constraints'
109+ option is True, this argument can be used as it would be in the
110+ traditional bigm transformation for the non-bound constraints.
109111 """
110112 ))
111113 CONFIG .declare ('reduce_bound_constraints' , ConfigValue (
@@ -140,6 +142,25 @@ class MultipleBigMTransformation(Transformation):
140142 Techniques," SIAM Review, vol. 57, no. 1, 2015, pp. 3-57.
141143 """
142144 ))
145+ CONFIG .declare ('only_mbigm_bound_constraints' , ConfigValue (
146+ default = False ,
147+ domain = bool ,
148+ description = "Flag indicating if only bound constraints should be "
149+ "transformed with multiple-bigm, or if all the disjunctive "
150+ "constraints should." ,
151+ doc = """
152+ Sometimes it is only computationally advantageous to apply multiple-
153+ bigm to disjunctive constraints with the special structure:
154+
155+ [l_1 <= x <= u_1] v [l_2 <= x <= u_2] v ... v [l_K <= x <= u_K],
156+
157+ and transform other disjunctive constraints with the traditional
158+ big-M transformation. This flag is used to set the above behavior.
159+
160+ Note that the reduce_bound_constraints flag must also be True when
161+ this flag is set to True.
162+ """
163+ ))
143164
144165 def __init__ (self ):
145166 super (MultipleBigMTransformation , self ).__init__ ()
@@ -171,6 +192,7 @@ def __init__(self):
171192 }
172193 self ._transformation_blocks = {}
173194 self ._algebraic_constraints = {}
195+ self ._arg_list = {}
174196
175197 def _apply_to (self , instance , ** kwds ):
176198 self .used_args = ComponentMap ()
@@ -180,6 +202,7 @@ def _apply_to(self, instance, **kwds):
180202 self .used_args .clear ()
181203 self ._transformation_blocks .clear ()
182204 self ._algebraic_constraints .clear ()
205+ self ._arg_list .clear ()
183206
184207 def _apply_to_impl (self , instance , ** kwds ):
185208 if not instance .ctype in (Block , Disjunct ):
@@ -191,6 +214,15 @@ def _apply_to_impl(self, instance, **kwds):
191214 self ._config = self .CONFIG (kwds .pop ('options' , {}))
192215 self ._config .set_value (kwds )
193216
217+ if (self ._config .only_mbigm_bound_constraints and not
218+ self ._config .reduce_bound_constraints ):
219+ raise GDP_Error ("The 'only_mbigm_bound_constraints' option is set "
220+ "to True, but the 'reduce_bound_constraints' "
221+ "option is not. This is not supported--please also "
222+ "set 'reduce_bound_constraints' to True if you "
223+ "only wish to transform the bound constraints with "
224+ "multiple bigm." )
225+
194226 targets = self ._config .targets
195227 knownBlocks = {}
196228 if targets is None :
@@ -272,9 +304,12 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct,
272304 transformed_constraints = self ._transform_bound_constraints (
273305 active_disjuncts , transBlock , arg_Ms )
274306
275- Ms = transBlock .calculated_missing_m_values = self .\
276- _calculate_missing_M_values (active_disjuncts , arg_Ms , transBlock ,
277- transformed_constraints )
307+ Ms = arg_Ms
308+ if not self ._config .only_mbigm_bound_constraints :
309+ Ms = transBlock .calculated_missing_m_values = self .\
310+ _calculate_missing_M_values (active_disjuncts , arg_Ms ,
311+ transBlock ,
312+ transformed_constraints )
278313
279314 # Now we can deactivate the constraints we deferred, so that we don't
280315 # re-transform them
@@ -396,31 +431,63 @@ def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms):
396431 name = unique_component_name (relaxationBlock , obj .getname (
397432 fully_qualified = False ))
398433
399- newConstraint = Constraint (obj . index_set (), transBlock . lbub )
434+ newConstraint = Constraint (Any )
400435 relaxationBlock .add_component (name , newConstraint )
436+ bigm = TransformationFactory ('gdp.bigm' )
437+ bigm .assume_fixed_vars_permanent = self ._config .\
438+ assume_fixed_vars_permanent
439+ bigm .used_args = self .used_args
401440
402441 for i in sorted (obj .keys ()):
403442 c = obj [i ]
404443 if not c .active :
405444 continue
406445
407- transformed = []
408- if c .lower is not None :
409- rhs = sum (Ms [c ,
410- disj ][0 ]* disj .indicator_var .get_associated_binary ()
411- for disj in active_disjuncts if disj is not disjunct )
412- newConstraint .add ((i , 'lb' ), c .body - c .lower >= rhs )
413- transformed .append (newConstraint [i , 'lb' ])
414-
415- if c .upper is not None :
416- rhs = sum (Ms [c ,
417- disj ][1 ]* disj .indicator_var .get_associated_binary ()
418- for disj in active_disjuncts if disj is not disjunct )
419- newConstraint .add ((i , 'ub' ), c .body - c .upper <= rhs )
420- transformed .append (newConstraint [i , 'ub' ])
421- for c_new in transformed :
422- constraintMap ['srcConstraints' ][c_new ] = [c ]
423- constraintMap ['transformedConstraints' ][c ] = transformed
446+ if not self ._config .only_mbigm_bound_constraints :
447+ transformed = []
448+ if c .lower is not None :
449+ rhs = sum (
450+ Ms [c ,
451+ disj ][0 ]* disj .indicator_var .get_associated_binary ()
452+ for disj in active_disjuncts if disj is not disjunct )
453+ newConstraint .add ((i , 'lb' ), c .body - c .lower >= rhs )
454+ transformed .append (newConstraint [i , 'lb' ])
455+
456+ if c .upper is not None :
457+ rhs = sum (
458+ Ms [c ,
459+ disj ][1 ]* disj .indicator_var .get_associated_binary ()
460+ for disj in active_disjuncts if disj is not disjunct )
461+ newConstraint .add ((i , 'ub' ), c .body - c .upper <= rhs )
462+ transformed .append (newConstraint [i , 'ub' ])
463+ for c_new in transformed :
464+ constraintMap ['srcConstraints' ][c_new ] = [c ]
465+ constraintMap ['transformedConstraints' ][c ] = transformed
466+ else :
467+ lower = (None , None , None )
468+ upper = (None , None , None )
469+
470+ if disjunct not in self ._arg_list :
471+ self ._arg_list [disjunct ] = bigm ._get_bigm_arg_list (
472+ self ._config .bigM , disjunct )
473+ arg_list = self ._arg_list [disjunct ]
474+
475+ # first, we see if an M value was specified in the arguments.
476+ # (This returns None if not)
477+ lower , upper = bigm ._get_M_from_args (c , Ms , arg_list , lower ,
478+ upper )
479+ M = (lower [0 ], upper [0 ])
480+
481+ # estimate if we don't have what we need
482+ if c .lower is not None and M [0 ] is None :
483+ M = (bigm ._estimate_M (c .body , c )[0 ] - c .lower , M [1 ])
484+ lower = (M [0 ], None , None )
485+ if c .upper is not None and M [1 ] is None :
486+ M = (M [0 ], bigm ._estimate_M (c .body , c )[1 ] - c .upper )
487+ upper = (M [1 ], None , None )
488+ bigm ._add_constraint_expressions (
489+ c , i , M , disjunct .indicator_var .get_associated_binary (),
490+ newConstraint , constraintMap )
424491
425492 # deactivate now that we have transformed
426493 c .deactivate ()
@@ -549,7 +616,7 @@ def _add_transformation_block(self, block):
549616 # transformed components
550617 transBlockName = unique_component_name (
551618 block ,
552- '_pyomo_gdp_hull_reformulation ' )
619+ '_pyomo_gdp_mbigm_reformulation ' )
553620 transBlock = Block ()
554621 block .add_component (transBlockName , transBlock )
555622 self ._transformation_blocks [block ] = transBlock
0 commit comments