Skip to content

Commit

Permalink
Merge pull request Pyomo#3412 from jsiirola/autoslots-deepcopy-fix
Browse files Browse the repository at this point in the history
Fix bug in AutoSlots deepcopy
  • Loading branch information
mrmundt authored Nov 12, 2024
2 parents 364e7f1 + e7bf41c commit af99289
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 16 deletions.
12 changes: 8 additions & 4 deletions pyomo/common/autoslots.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,13 @@ def __deepcopy__(self, memo):
# Note: this implementation avoids deepcopying the temporary
# 'state' list, significantly speeding things up.
memo[id(self)] = ans = self.__class__.__new__(self.__class__)
ans.__setstate__(
[fast_deepcopy(field, memo) for field in self.__getstate__()]
)
state = self.__getstate__()
ans.__setstate__([fast_deepcopy(field, memo) for field in state])
# The state uses a temporary dict to store the (mapped)
# __dict__ state. It is important that we DO NOT save the
# id() of that temporary object in the memo
if self.__auto_slots__.has_dict:
del memo[id(state[-1])]
return ans

def __getstate__(self):
Expand Down Expand Up @@ -300,7 +304,7 @@ def __getstate__(self):
if self.__auto_slots__.has_dict:
fields = dict(self.__dict__)
# Map (encode) any field values. It is not an error if
# the field if not present.
# the field is not present.
for name, mapper in self.__auto_slots__.field_mappers.items():
if name in fields:
fields[name] = mapper(True, fields[name])
Expand Down
21 changes: 12 additions & 9 deletions pyomo/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,7 @@ def _data(self, value):


class ConfigBase(object):
# Note: __getstate__ relies on this field ordering. Do not change.
__slots__ = (
'_parent',
'_domain',
Expand Down Expand Up @@ -1799,20 +1800,19 @@ def __getstate__(self):
# can allocate the state dictionary. If it is not, then we call
# the super-class's __getstate__ (since that class is NOT
# 'object').
state = [('_domain', _picklable(self._domain, self))]
state = [None, _picklable(self._domain, self)]
# Note: [2:] skips _parent and _domain (intentionally): We just
# wrapped _domain in _picklable and will restore _parent in
# __setstate__
state.extend((key, getattr(self, key)) for key in ConfigBase.__slots__[2:])
# wrapped _domain in _picklable and explicitly set _parent to
# None (it will be restored in __setstate__).
state.extend(getattr(self, key) for key in ConfigBase.__slots__[2:])
return state

def __setstate__(self, state):
for key, val in state:
for key, val in zip(ConfigBase.__slots__, state):
# Note: per the Python data model docs, we explicitly
# set the attribute using object.__setattr__() instead
# of setting self.__dict__[key] = val.
object.__setattr__(self, key, val)
self._parent = None

def __call__(
self,
Expand Down Expand Up @@ -2605,6 +2605,7 @@ class ConfigDict(ConfigBase, Mapping):

content_filters = {None, 'all', 'userdata'}

# Note: __getstate__ relies on this field ordering. Do not change.
__slots__ = ('_implicit_domain', '_declared', '_implicit_declaration')
_reserved_words = set()

Expand Down Expand Up @@ -2634,14 +2635,16 @@ def domain_name(self):

def __getstate__(self):
state = super().__getstate__()
state.append(('_implicit_domain', _picklable(self._implicit_domain, self)))
state.append(_picklable(self._implicit_domain, self))
# Note: [1:] intentionally skips the _implicit_domain (which we
# just handled)
state.extend((key, getattr(self, key)) for key in ConfigDict.__slots__[1:])
state.extend(getattr(self, key) for key in ConfigDict.__slots__[1:])
return state

def __setstate__(self, state):
state = super().__setstate__(state)
super().__setstate__(state)
for key, val in zip(ConfigDict.__slots__, state[len(ConfigBase.__slots__) :]):
object.__setattr__(self, key, val)
for x in self._data.values():
x._parent = self

Expand Down
7 changes: 4 additions & 3 deletions pyomo/core/base/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ def __contains__(self, key):
TODO
"""
# Return True is the underlying Block contains the component
# name. Note, if this Pseudomap soecifies a ctype or the
# name. Note, if this Pseudomap specifies a ctype or the
# active flag, we need to check that the underlying
# component matches those flags
if key in self._block._decl:
Expand Down Expand Up @@ -1948,8 +1948,9 @@ def _create_objects_for_deepcopy(self, memo, component_list):
# deepcopy() from violating the Python recursion limit.
# This step is recursive; however, we do not expect "super
# deep" Pyomo block hierarchies, so should be okay.
for comp in self.component_map().values():
comp._create_objects_for_deepcopy(memo, component_list)
for comp in self._decl_order:
if comp[0] is not None:
comp[0]._create_objects_for_deepcopy(memo, component_list)
return _ans

def private_data(self, scope=None):
Expand Down
3 changes: 3 additions & 0 deletions pyomo/core/expr/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,9 @@ def visiting_potential_leaf(self, node):
if node is None:
return True, None

if node.__class__ in native_numeric_types:
return True, str(node)

if node.__class__ in nonpyomo_leaf_types:
return True, repr(node)

Expand Down

0 comments on commit af99289

Please sign in to comment.