|
14 | 14 | logger = logging.getLogger('pyomo.core')
|
15 | 15 |
|
16 | 16 | from pyomo.common import deprecated
|
17 |
| -from pyomo.core.base import Transformation, TransformationFactory, Var, Suffix, Reals |
| 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 |
18 | 34 |
|
19 | 35 |
|
20 | 36 | #
|
|
25 | 41 | 'core.relax_integer_vars', doc="Relax integer variables to continuous counterparts"
|
26 | 42 | )
|
27 | 43 | 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 | + |
28 | 115 | def __init__(self):
|
29 |
| - super(RelaxIntegerVars, self).__init__() |
| 116 | + super().__init__() |
30 | 117 |
|
31 | 118 | def _apply_to(self, model, **kwds):
|
32 |
| - options = kwds.pop('options', {}) |
33 |
| - if kwds.get('undo', options.get('undo', False)): |
| 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 | + ) |
34 | 133 | for v, d in model._relaxed_integer_vars[None].values():
|
35 | 134 | bounds = v.bounds
|
36 | 135 | v.domain = d
|
37 | 136 | v.setlb(bounds[0])
|
38 | 137 | v.setub(bounds[1])
|
39 | 138 | model.del_component("_relaxed_integer_vars")
|
40 | 139 | 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 |
47 | 140 |
|
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: |
| 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: |
54 | 211 | if not var.is_integer():
|
55 | 212 | continue
|
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 |
| 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) |
78 | 219 |
|
79 | 220 |
|
80 | 221 | @TransformationFactory.register(
|
|
0 commit comments