Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document BlockData.clone #3542

Merged
merged 3 commits into from
Mar 28, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions pyomo/core/base/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading