|
14 | 14 | logger = logging.getLogger('pyomo.core') |
15 | 15 |
|
16 | 16 | from pyomo.common import deprecated |
17 | | -from pyomo.common.config import ConfigDict, ConfigValue, In, IsInstance |
18 | | -from pyomo.common.deprecation import deprecation_warning |
19 | | -from pyomo.core.base import ( |
20 | | - Transformation, |
21 | | - TransformationFactory, |
22 | | - Var, |
23 | | - Suffix, |
24 | | - Reals, |
25 | | - Block, |
26 | | - ReverseTransformationToken, |
27 | | - VarCollector, |
28 | | - Constraint, |
29 | | - Objective, |
30 | | -) |
31 | | -from pyomo.core.util import target_list |
32 | | -from pyomo.gdp import Disjunct |
33 | | -from pyomo.util.vars_from_expressions import get_vars_from_components |
| 17 | +from pyomo.core.base import Transformation, TransformationFactory, Var, Suffix, Reals |
34 | 18 |
|
35 | 19 |
|
36 | 20 | # |
|
41 | 25 | 'core.relax_integer_vars', doc="Relax integer variables to continuous counterparts" |
42 | 26 | ) |
43 | 27 | class RelaxIntegerVars(Transformation): |
44 | | - CONFIG = ConfigDict('core.relax_integer_vars') |
45 | | - CONFIG.declare( |
46 | | - 'targets', |
47 | | - ConfigValue( |
48 | | - default=None, |
49 | | - domain=target_list, |
50 | | - description="target or list of targets that will be relaxed", |
51 | | - doc=""" |
52 | | - This specifies the list of components to relax. If None (default), the |
53 | | - entire model is transformed. Note that if the transformation is done |
54 | | - out of place, the list of targets should be attached to the model before |
55 | | - it is cloned, and the list will specify the targets on the cloned |
56 | | - instance.""", |
57 | | - ), |
58 | | - ) |
59 | | - CONFIG.declare( |
60 | | - 'reverse', |
61 | | - ConfigValue( |
62 | | - default=None, |
63 | | - domain=IsInstance(ReverseTransformationToken), |
64 | | - description="The token returned by a (forward) call to this " |
65 | | - "transformation, if you wish to reverse the transformation.", |
66 | | - doc=""" |
67 | | - This argument should be the reverse transformation token |
68 | | - returned by a previous call to this transformation to transform |
69 | | - fixed disjunctive state in the given model. |
70 | | - If this argument is specified, this call to the transformation |
71 | | - will reverse what the transformation did in the call that returned |
72 | | - the token. Note that if there are intermediate changes to the model |
73 | | - in between the forward and the backward calls to the transformation, |
74 | | - the behavior could be unexpected. |
75 | | - """, |
76 | | - ), |
77 | | - ) |
78 | | - CONFIG.declare( |
79 | | - 'var_collector', |
80 | | - ConfigValue( |
81 | | - default=VarCollector.FromVarComponents, |
82 | | - domain=In(VarCollector), |
83 | | - description="The method for collection the Vars to relax. If " |
84 | | - "VarCollector.FromVarComponents (default), any Var component on " |
85 | | - "the active tree will be relaxed.", |
86 | | - doc=""" |
87 | | - This specifies the method for collecting the Var components to relax. |
88 | | - The default, VarCollector.FromVarComponents, assumes that all relevant |
89 | | - Vars are on the active tree. If this is true, then this is the most |
90 | | - performant option. However, in more complex cases where some Vars may not |
91 | | - be in the active tree (e.g. some are on deactivated Blocks or come from |
92 | | - other models), specify VarCollector.FromExpressions to relax all Vars that |
93 | | - appear in expressions in the active tree. |
94 | | - """, |
95 | | - ), |
96 | | - ) |
97 | | - CONFIG.declare( |
98 | | - 'transform_deactivated_blocks', |
99 | | - ConfigValue( |
100 | | - default=True, |
101 | | - description="[DEPRECATED]: Whether or not to search for Var components to " |
102 | | - "relax on deactivated Blocks. True by default", |
103 | | - ), |
104 | | - ) |
105 | | - CONFIG.declare( |
106 | | - 'undo', |
107 | | - ConfigValue( |
108 | | - default=False, |
109 | | - domain=bool, |
110 | | - description="[DEPRECATED]: Please use the 'reverse' argument to undo " |
111 | | - "the transformation.", |
112 | | - ), |
113 | | - ) |
114 | | - |
115 | 28 | def __init__(self): |
116 | | - super().__init__() |
| 29 | + super(RelaxIntegerVars, self).__init__() |
117 | 30 |
|
118 | 31 | def _apply_to(self, model, **kwds): |
119 | | - if not model.ctype in (Block, Disjunct): |
120 | | - raise ValueError( |
121 | | - "Transformation called on %s of type %s. 'model' " |
122 | | - "must be a ConcreteModel or Block." % (model.name, model.ctype) |
123 | | - ) |
124 | | - config = self.CONFIG(kwds.pop('options', {})) |
125 | | - config.set_value(kwds) |
126 | | - |
127 | | - if config.undo: |
128 | | - deprecation_warning( |
129 | | - "The 'undo' argument is deprecated. Please use the 'reverse' " |
130 | | - "argument to undo the transformation.", |
131 | | - version='6.9.3.dev0', |
132 | | - ) |
| 32 | + options = kwds.pop('options', {}) |
| 33 | + if kwds.get('undo', options.get('undo', False)): |
133 | 34 | for v, d in model._relaxed_integer_vars[None].values(): |
134 | 35 | bounds = v.bounds |
135 | 36 | v.domain = d |
136 | 37 | v.setlb(bounds[0]) |
137 | 38 | v.setub(bounds[1]) |
138 | 39 | model.del_component("_relaxed_integer_vars") |
139 | 40 | return |
| 41 | + # True by default, you can specify False if you want |
| 42 | + descend = kwds.get( |
| 43 | + 'transform_deactivated_blocks', |
| 44 | + options.get('transform_deactivated_blocks', True), |
| 45 | + ) |
| 46 | + active = None if descend else True |
140 | 47 |
|
141 | | - targets = (model,) if config.targets is None else config.targets |
142 | | - |
143 | | - if config.reverse is None: |
144 | | - reverse_dict = {} |
145 | | - # Relax the model |
146 | | - reverse_token = ReverseTransformationToken( |
147 | | - self.__class__, model, targets, reverse_dict |
148 | | - ) |
149 | | - else: |
150 | | - # reverse the transformation |
151 | | - reverse_token = config.reverse |
152 | | - reverse_token.check_token_valid(self.__class__, model, targets) |
153 | | - reverse_dict = reverse_token.reverse_dict |
154 | | - for v, d in reverse_dict.values(): |
155 | | - lb, ub = v.bounds |
156 | | - v.domain = d |
157 | | - v.setlb(lb) |
158 | | - v.setub(ub) |
159 | | - return |
160 | | - |
161 | | - ### [ESJ 4/29/25]: This can go away when we remove 'undo' |
162 | | - model._relaxed_integer_vars = Suffix(direction=Suffix.LOCAL) |
163 | | - model._relaxed_integer_vars[None] = reverse_dict |
164 | | - ### |
165 | | - |
166 | | - for t in targets: |
167 | | - if isinstance(t, Block): |
168 | | - blocks = t.values() if t.is_indexed() else (t,) |
169 | | - for block in blocks: |
170 | | - self._relax_block(block, config, reverse_dict) |
171 | | - elif t.ctype is Var: |
172 | | - self._relax_var(t, reverse_dict) |
173 | | - else: |
174 | | - raise ValueError( |
175 | | - "Target '%s' was not a Block or Var. It was of type " |
176 | | - "'%s' and cannot be transformed." % (t.name, type(t)) |
177 | | - ) |
178 | | - |
179 | | - return reverse_token |
180 | | - |
181 | | - def _relax_block(self, block, config, reverse_dict): |
182 | | - self._relax_vars_from_block(block, config, reverse_dict) |
183 | | - |
184 | | - for b in block.component_data_objects(Block, active=None, descend_into=True): |
185 | | - if not b.active: |
186 | | - if config.transform_deactivated_blocks: |
187 | | - deprecation_warning( |
188 | | - "The `transform_deactivated_blocks` arguments is deprecated. " |
189 | | - "Either specify deactivated Blocks as targets to activate them " |
190 | | - "if transforming them is the desired behavior.", |
191 | | - version='6.9.3.dev0', |
192 | | - ) |
193 | | - else: |
194 | | - continue |
195 | | - self._relax_vars_from_block(b, config, reverse_dict) |
196 | | - |
197 | | - def _relax_vars_from_block(self, block, config, reverse_dict): |
198 | | - if config.var_collector is VarCollector.FromVarComponents: |
199 | | - model_vars = block.component_data_objects(Var, descend_into=False) |
200 | | - else: |
201 | | - model_vars = get_vars_from_components( |
202 | | - block, ctype=(Constraint, Objective), descend_into=False |
203 | | - ) |
204 | | - for var in model_vars: |
205 | | - if id(var) not in reverse_dict: |
206 | | - self._relax_var(var, reverse_dict) |
207 | | - |
208 | | - def _relax_var(self, v, reverse_dict): |
209 | | - var_datas = v.values() if v.is_indexed() else (v,) |
210 | | - for var in var_datas: |
| 48 | + # Relax the model |
| 49 | + relaxed_vars = {} |
| 50 | + _base_model_vars = model.component_data_objects( |
| 51 | + Var, active=active, descend_into=True |
| 52 | + ) |
| 53 | + for var in _base_model_vars: |
211 | 54 | if not var.is_integer(): |
212 | 55 | continue |
213 | | - lb, ub = var.bounds |
214 | | - _domain = var.domain |
215 | | - var.domain = Reals |
216 | | - var.setlb(lb) |
217 | | - var.setub(ub) |
218 | | - reverse_dict[id(var)] = (var, _domain) |
| 56 | + # Note: some indexed components can only have their |
| 57 | + # domain set on the parent component (the individual |
| 58 | + # indices cannot be set independently) |
| 59 | + _c = var.parent_component() |
| 60 | + try: |
| 61 | + lb, ub = var.bounds |
| 62 | + _domain = var.domain |
| 63 | + var.domain = Reals |
| 64 | + var.setlb(lb) |
| 65 | + var.setub(ub) |
| 66 | + relaxed_vars[id(var)] = (var, _domain) |
| 67 | + except: |
| 68 | + if id(_c) in relaxed_vars: |
| 69 | + continue |
| 70 | + _domain = _c.domain |
| 71 | + lb, ub = _c.bounds |
| 72 | + _c.domain = Reals |
| 73 | + _c.setlb(lb) |
| 74 | + _c.setub(ub) |
| 75 | + relaxed_vars[id(_c)] = (_c, _domain) |
| 76 | + model._relaxed_integer_vars = Suffix(direction=Suffix.LOCAL) |
| 77 | + model._relaxed_integer_vars[None] = relaxed_vars |
219 | 78 |
|
220 | 79 |
|
221 | 80 | @TransformationFactory.register( |
|
0 commit comments