Skip to content

Commit 332bee1

Browse files
authored
Revert "Rewrite core.relax_integer_vars transformation"
1 parent ca33341 commit 332bee1

File tree

4 files changed

+40
-381
lines changed

4 files changed

+40
-381
lines changed

pyomo/core/base/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from pyomo.core.base.component import name, Component, ModelComponentFactory
3939
from pyomo.core.base.componentuid import ComponentUID
4040
from pyomo.core.base.config import PyomoOptions
41-
from pyomo.core.base.enums import SortComponents, TraversalStrategy, VarCollector
41+
from pyomo.core.base.enums import SortComponents, TraversalStrategy
4242
from pyomo.core.base.label import (
4343
CuidLabeler,
4444
CounterLabeler,

pyomo/core/base/enums.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
import enum
1313
import sys
14-
from pyomo.common import enums
1514

1615
if sys.version_info[:2] >= (3, 11):
1716
strictEnum = {'boundary': enum.STRICT}
@@ -94,8 +93,3 @@ def sort_names(flag):
9493
@staticmethod
9594
def sort_indices(flag):
9695
return SortComponents.SORTED_INDICES in SortComponents(flag)
97-
98-
99-
class VarCollector(enums.IntEnum):
100-
FromVarComponents = 1
101-
FromExpressions = 2

pyomo/core/plugins/transform/discrete_vars.py

Lines changed: 38 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,7 @@
1414
logger = logging.getLogger('pyomo.core')
1515

1616
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
3418

3519

3620
#
@@ -41,181 +25,56 @@
4125
'core.relax_integer_vars', doc="Relax integer variables to continuous counterparts"
4226
)
4327
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-
11528
def __init__(self):
116-
super().__init__()
29+
super(RelaxIntegerVars, self).__init__()
11730

11831
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)):
13334
for v, d in model._relaxed_integer_vars[None].values():
13435
bounds = v.bounds
13536
v.domain = d
13637
v.setlb(bounds[0])
13738
v.setub(bounds[1])
13839
model.del_component("_relaxed_integer_vars")
13940
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
14047

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:
21154
if not var.is_integer():
21255
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
21978

22079

22180
@TransformationFactory.register(

0 commit comments

Comments
 (0)