diff --git a/pyomo/common/autoslots.py b/pyomo/common/autoslots.py index 89fefaf4f21..5846fbb443c 100644 --- a/pyomo/common/autoslots.py +++ b/pyomo/common/autoslots.py @@ -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): @@ -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]) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index fa59d53ead1..b6ff0ebfee3 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1744,6 +1744,7 @@ def _data(self, value): class ConfigBase(object): + # Note: __getstate__ relies on this field ordering. Do not change. __slots__ = ( '_parent', '_domain', @@ -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, @@ -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() @@ -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 diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 526c4c8bb41..8da80ec1163 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -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: @@ -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): diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 3059873cd7d..b172055c8e3 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -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)