pyomo icon indicating copy to clipboard operation
pyomo copied to clipboard

Cannot clone individual blocks within an indexed block

Open michaelbynum opened this issue 4 years ago • 2 comments

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 avatar Jun 07 '21 22:06 michaelbynum

@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?

jsiirola avatar Jun 07 '21 22:06 jsiirola

Excellent question. I'm not sure. Returning a ScalarBlock might make sense.

michaelbynum avatar Jun 08 '21 02:06 michaelbynum

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()

jsiirola avatar Aug 22 '22 14:08 jsiirola