diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 86d9f930bda..dd0bc790b68 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1243,8 +1243,83 @@ def reclassify_component_type( self._decl_order[idx] = (obj, tmp) def clone(self, memo=None): - """ - TODO + """Make a copy of this block (and all components contained in it). + + Pyomo models use :py:class:`Block` components to define a + hierarchical structure and provide model scoping. When modeling + :py:class:`~pyomo.core.base.component.Component` objects are + assigned to a block, they are automatically added to that block's + scope. + + :py:meth:`clone()` implements a specialization of + :py:func:`copy.deepcopy` that will deep copy the + :py:class:`BlockData` using that block's scope: that is, copy + the :py:class:`BlockData` and (recursively) all + :py:class:`Component` objects attached to it (including any + sub-blocks). Pyomo + :py:class:`~pyomo.core.base.component.Component` / + :py:class:`~pyomo.core.base.component.ComponentData` objects + that are referenced through objects on this block but are not in + this block scope (i.e., are not owned by this block or a + subblock of this block) are not duplicated. + + Parameters + ---------- + memo : dict + A user-defined memo dictionary. The dictionary will be + updated by :py:meth:`clone` and :py:func:`copy.deepcopy`. + See :py:meth:`object.__deepcopy__` for more information. + + Examples + -------- + Given the following model: + + >>> m = pyo.ConcreteModel() + >>> m.I = pyo.RangeSet(3) + >>> m.x = pyo.Var() + >>> m.b1 = pyo.Block() + >>> m.b1.J = pyo.RangeSet(3) + >>> m.b1.y = pyo.Var(domain=pyo.Reals) + >>> m.b1.z = pyo.Var(m.I) + >>> m.b1.c = pyo.Constraint(expr=m.x >= m.b1.y + sum(m.b1.z[:])) + >>> m.b1.b2 = pyo.Block() + >>> m.b1.b2.w = pyo.Var(m.b1.J) + >>> m.b1.d = pyo.Constraint(expr=m.b1.y + sum(m.b1.b2.w[:]) == 5) + + If we clone a block: + + >>> i = m.b1.clone() + + All local components are copied: + + >>> assert m.b1 is not i + >>> assert m.b1.J is not i.J + >>> assert m.b1.y is not i.y + >>> assert m.b1.z is not i.z + >>> assert m.b1.b2 is not i.b2 + >>> assert m.b1.b2.w is not i.b2.w + + References to local components (in this case, Sets) are copied + and updated: + + >>> assert m.b1.b2.w.index_set() is not i.b2.w.index_set() + + But references to out-of-scope Sets (either global or in a + different block scope) are preserved: + + >>> assert m.b1.y.index_set() is i.y.index_set() + >>> assert m.b1.z.index_set() is i.z.index_set() + >>> assert m.b1.y.domain is i.y.domain + + Expressions are also updated in a similar manner: the new + expression will reference the new (copied) components for any + components in scope, but references to out-of-scope components + will be preserved: + + >>> from pyomo.core.expr.compare import compare_expressions + >>> assert compare_expressions(i.c.expr, m.x >= i.y + sum(i.z[:])) + >>> assert compare_expressions(i.d.expr, i.y + sum(i.b2.w[:]) == 5) + """ # FYI: we used to remove all _parent() weakrefs before # deepcopying and then restore them on the original and cloned