pyomo
pyomo copied to clipboard
Cannot clone individual blocks within an indexed block
Summary
_BlockData does not have a _parent attribute, so it cannot be cloned.
Steps to reproduce the issue
In [1]: import pyomo.environ as pe
In [2]: m = pe.ConcreteModel()
In [3]: m.b = pe.Block([1,2,3])
In [4]: m.b[1].clone()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-bb20e18a9495> in <module>
----> 1 m.b[1].clone()
~/repos/pyomo/pyomo/core/base/block.py in clone(self)
1232 # should be preserved as singletons.
1233 #
-> 1234 save_parent, self._parent = self._parent, None
1235 try:
1236 new_block = copy.deepcopy(
~/repos/pyomo/pyomo/core/base/block.py in __getattr__(self, val)
522 # throw the "normal" AttributeError
523 raise AttributeError("'%s' object has no attribute '%s'"
--> 524 % (self.__class__.__name__, val))
525
526 def __setattr__(self, name, val):
AttributeError: '_BlockData' object has no attribute '_parent'
@michaelbynum: what return type would you expect for m.b[1].clone()? The clone() infrastructure (since it is based on deepcopy()) would give you a _BlockData: but that raises another pickle, as the current Pyomo design won't pick up a bare _BlockData on a model. That is m.c = m.b[1].clone() would result in type(m.c) == _BlockData (and not ScalarBlock), so it wouldn't be picked up / indexed by the PseudoMap that stores the m Block's components.
Should we add special handling so that cloning _BlockData returns a ScalarBlock?
Excellent question. I'm not sure. Returning a ScalarBlock might make sense.
Note that #2504 proposes a simple fix (so that cloning works). However, it takes the "least surprising route for someone who knows Pyomo" and just returns the expected _BlockData. Users who want to attach hat block to a model would need to define a new Block (scalar or indexed) and transfer the attributes. For example:
This resolves #2008 and allows individual _BlockData to be cloned(). Note that the result is itself a _BlockData and should not be directly attached to a model. Instead, the result should be passed to another block initializer / rule. For example:
m = ConcreteModel()
@m.Block([1,2,3])
def blk(b, i):
b.IDX = RangeSet(i)
b.x = Var(b.IDX)
m.c = Block(rule=m.blk[2].clone())
m.d = Block()
m.d.transfer_attributes_from(m.blk[3].clone())
@m.Block([1,2,3])
def e(b, i):
return b.model().blk[i].clone()