Skip to content

Commit 025a2bb

Browse files
authored
Merge pull request #2628 from jsiirola/deepcopy-improvements
Deepcopy improvements
2 parents ece4f7b + c3c19d5 commit 025a2bb

File tree

3 files changed

+50
-45
lines changed

3 files changed

+50
-45
lines changed

pyomo/common/autoslots.py

+21-13
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,26 @@
2020
)
2121

2222
def _deepcopy_tuple(obj, memo, _id):
23-
_unchanged.append(True)
24-
ans = tuple(fast_deepcopy(x, memo) for x in obj)
25-
if _unchanged.pop():
26-
# It appears to be faster *not* to cache the fact that this
27-
# particular tuple was unchanged by the deepcopy
28-
# memo[_id] = obj
23+
ans = []
24+
unchanged = True
25+
for item in obj:
26+
new_item = fast_deepcopy(item, memo)
27+
ans.append(new_item)
28+
if new_item is not item:
29+
unchanged = False
30+
if unchanged:
31+
# Python does not duplicate "unchanged" tuples (i.e. allows the
32+
# original objecct to be returned from deepcopy()). We will
33+
# preserve that behavior here.
34+
#
35+
# It also appears to be faster *not* to cache the fact that this
36+
# particular tuple was unchanged by the deepcopy (Note: the
37+
# standard library also does not cache the unchanged tuples in
38+
# the memo)
39+
#
40+
# memo[_id] = obj
2941
return obj
30-
memo[_id] = ans
42+
memo[_id] = ans = tuple(ans)
3143
return ans
3244

3345
def _deepcopy_list(obj, memo, _id):
@@ -55,7 +67,6 @@ def _deepcopier(obj, memo, _id):
5567
dict: _deepcopy_dict,
5668
}
5769

58-
_unchanged = [None]
5970

6071
def fast_deepcopy(obj, memo):
6172
"""A faster implementation of copy.deepcopy()
@@ -70,12 +81,9 @@ def fast_deepcopy(obj, memo):
7081
return obj
7182
_id = id(obj)
7283
if _id in memo:
73-
ans = memo[_id]
84+
return memo[_id]
7485
else:
75-
ans = _deepcopy_mapper.get(obj.__class__, _deepcopier)(obj, memo, _id)
76-
if ans is not obj:
77-
_unchanged[-1] = False
78-
return ans
86+
return _deepcopy_mapper.get(obj.__class__, _deepcopier)(obj, memo, _id)
7987

8088

8189
class AutoSlots(type):

pyomo/core/base/component.py

+22-19
Original file line numberDiff line numberDiff line change
@@ -107,32 +107,35 @@ def __deepcopy__(self, memo):
107107
# we need to override __deepcopy__ for both Component and
108108
# ComponentData.
109109
#
110-
# Note: there is an edge case when cloning a block: the initial
111-
# call to deepcopy (on the target block) has __block_scope__
112-
# defined, however, the parent block of self is either None, or
113-
# is (by definition) out of scope. So we will check that
114-
# id(self) is not in __block_scope__: if it is, then this is the
115-
# top-level block and we need to do the normal deepcopy.
116-
if '__block_scope__' in memo and \
117-
id(self) not in memo['__block_scope__']:
118-
_known = memo['__block_scope__']
119-
_new = []
110+
if '__block_scope__' in memo:
111+
_scope = memo['__block_scope__']
112+
_new = None
120113
tmp = self.parent_block()
121-
tmpId = id(tmp)
122114
# Note: normally we would need to check that tmp does not
123115
# end up being None. However, since clone() inserts
124116
# id(None) into the __block_scope__ dictionary, we are safe
125-
while tmpId not in _known:
126-
_new.append(tmpId)
117+
while id(tmp) not in _scope:
118+
_new = (_new, id(tmp))
127119
tmp = tmp.parent_block()
128-
tmpId = id(tmp)
120+
_in_scope = _scope[id(tmp)]
129121

130122
# Remember whether all newly-encountered blocks are in or
131123
# out of scope (prevent duplicate work)
132-
for _id in _new:
133-
_known[_id] = _known[tmpId]
134-
135-
if not _known[tmpId]:
124+
while _new is not None:
125+
_new, _id = _new
126+
_scope[_id] = _in_scope
127+
128+
# Note: there is an edge case when cloning a block: the
129+
# initial call to deepcopy (on the target block) has
130+
# __block_scope__ defined, however, the parent block of self
131+
# is either None, or is (by definition) out of scope. So we
132+
# will check that id(self) is not in __block_scope__: if it
133+
# is, then this is the top-level block and we need to do the
134+
# normal deepcopy. We defer this check until now for
135+
# efficiency reasons beause we expect that (for sane models)
136+
# the bulk of the ccomponents we will encounter will be *in*
137+
# scope.
138+
if not _in_scope and id(self) not in _scope:
136139
# component is out-of-scope. shallow copy only
137140
memo[id(self)] = self
138141
return self
@@ -258,7 +261,7 @@ def _create_objects_for_deepcopy(self, memo, component_list):
258261
def _deepcopy_field(self, memo, slot_name, value):
259262
saved_memo = len(memo)
260263
try:
261-
return deepcopy(value, memo)
264+
return fast_deepcopy(value, memo)
262265
except CloneError:
263266
raise
264267
except:

pyomo/core/base/indexed_component.py

+7-13
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pyomo.core.base.config import PyomoOptions
2828
from pyomo.core.base.global_set import UnindexedComponent_set
2929
from pyomo.common import DeveloperError
30+
from pyomo.common.autoslots import fast_deepcopy
3031
from pyomo.common.dependencies import numpy as np, numpy_available
3132
from pyomo.common.deprecation import deprecated
3233
from pyomo.common.modeling import NOTSET
@@ -330,19 +331,12 @@ def _create_objects_for_deepcopy(self, memo, component_list):
330331
# Because we are already checking / updating the memo
331332
# for the _data dict, we can effectively "deepcopy" it
332333
# right now (almost for free!)
333-
memo[id(self._data)] = _new._data = _data = {}
334-
for idx, obj in self._data.items():
335-
# We need to deepcopy the index, but deepcopying
336-
# tuples in Python is SLOW. We will only deepcopy
337-
# if necessary.
338-
if idx.__class__ is tuple:
339-
if any(x.__class__ not in native_types for x in idx):
340-
idx = deepcopy(idx, memo)
341-
elif idx.__class__ not in native_types:
342-
idx = deepcopy(idx, memo)
343-
344-
_data[idx] = obj._create_objects_for_deepcopy(
345-
memo, component_list)
334+
_src = self._data
335+
memo[id(_src)] = _new._data = _data = _src.__class__()
336+
for idx, obj in _src.items():
337+
_data[fast_deepcopy(idx, memo)] \
338+
= obj._create_objects_for_deepcopy(memo, component_list)
339+
346340
return _ans
347341

348342
def to_dense_data(self):

0 commit comments

Comments
 (0)