# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________
import copy
import weakref
from pyomo.common.autoslots import AutoSlots
def _not_implemented(*args, **kwds):
raise NotImplementedError("This property is abstract") # pragma:nocover
def _abstract_readwrite_property(**kwds):
p = property(fget=_not_implemented, fset=_not_implemented, **kwds)
return p
def _abstract_readonly_property(**kwds):
p = property(fget=_not_implemented, **kwds)
return p
class _no_ctype(object):
"""The default argument for methods that accept a ctype."""
pass
# This will be populated outside of core.kernel. It will map
# AML classes (which are the ctypes used by all of the
# solver interfaces) to Kernel classes
_convert_ctype = {}
_kernel_ctype_backmap = {}
def _convert_descend_into(value):
"""Converts the descend_into keyword to a function"""
if hasattr(value, "__call__"):
return value
elif value:
return _convert_descend_into._true
else:
return _convert_descend_into._false
_convert_descend_into._true = lambda x: True
_convert_descend_into._false = lambda x: False
[docs]
class ICategorizedObject(AutoSlots.Mixin):
"""
Interface for objects that maintain a weak reference to
a parent storage object and have a category type.
This class is abstract. It assumes any derived class
declares the attributes below with or without slots:
Attributes:
_ctype: Stores the object's category type, which
should be some class derived from
ICategorizedObject. This attribute may be
declared at the class level.
_parent: Stores a weak reference to the object's
parent container or :const:`None`.
_storage_key: Stores key this object can be accessed
with through its parent container.
_active (bool): Stores the active status of this
object.
"""
__slots__ = ()
__autoslot_mappers__ = {'_parent': AutoSlots.weakref_mapper}
# These flags can be used by implementations
# to avoid isinstance calls.
_is_container = False
"""A flag used to indicate that the class is an instance
of ICategorizedObjectContainer."""
_is_heterogeneous_container = False
"""A flag used to indicate that the class is an instance
of ICategorizedObjectContainer that stores objects with
different category types than its own."""
#
# Interface
#
@property
def ctype(self):
"""The object's category type."""
return self._ctype
@property
def parent(self):
"""The object's parent (possibly None)."""
if isinstance(self._parent, weakref.ReferenceType):
return self._parent()
else:
return self._parent
@property
def storage_key(self):
"""The object's storage key within its parent"""
return self._storage_key
@property
def active(self):
"""The active status of this object."""
return self._active
@active.setter
def active(self, value):
raise AttributeError("Assignment not allowed. Use the (de)activate method")
### The following group of methods use object.__setattr__
### to update the _parent, _storage_key, and _active flags.
### This is done to allow the block implementation (block.py)
### to protect them from being overwritten with user added
### components in its __setattr__ method
def _update_parent_and_storage_key(self, parent, key):
object.__setattr__(self, "_parent", weakref.ref(parent))
object.__setattr__(self, "_storage_key", key)
def _clear_parent_and_storage_key(self):
object.__setattr__(self, "_parent", None)
object.__setattr__(self, "_storage_key", None)
[docs]
def activate(self):
"""Activate this object."""
object.__setattr__(self, "_active", True)
[docs]
def deactivate(self):
"""Deactivate this object."""
object.__setattr__(self, "_active", False)
###
[docs]
def getname(
self,
fully_qualified=False,
name_buffer={}, # HACK: ignored (required to work with some solver interfaces, but that code should change soon)
convert=str,
relative_to=None,
):
"""
Dynamically generates a name for this object.
Args:
fully_qualified (bool): Generate a full name by
iterating through all ancestor containers.
Default is :const:`False`.
convert (function): A function that converts a
storage key into a string
representation. Default is the built-in
function str.
relative_to (object): When generating a fully
qualified name, generate the name relative
to this block.
Returns:
If a parent exists, this method returns a string
representing the name of the object in the
context of its parent; otherwise (if no parent
exists), this method returns :const:`None`.
"""
assert fully_qualified or (relative_to is None)
parent = self.parent
if parent is None:
return None
key = self.storage_key
name = parent._child_storage_entry_string % convert(key)
if fully_qualified:
parent_name = parent.getname(fully_qualified=True, relative_to=relative_to)
if (parent_name is not None) and (
(relative_to is None) or (parent is not relative_to)
):
return parent_name + parent._child_storage_delimiter_string + name
else:
return name
else:
return name
@property
def name(self):
"""The object's fully qualified name. Alias for
`obj.getname(fully_qualified=True)`."""
return self.getname(fully_qualified=True)
@property
def local_name(self):
"""The object's local name within the context of its
parent. Alias for
`obj.getname(fully_qualified=False)`."""
return self.getname(fully_qualified=False)
def __str__(self):
"""Convert this object to a string by first
attempting to generate its fully qualified name. If
the object does not have a name (because it does not
have a parent, then a string containing the class
name is returned."""
name = self.name
if name is None:
return "<" + self.__class__.__name__ + ">"
else:
return name
[docs]
def clone(self):
"""
Returns a copy of this object with the parent
pointer set to :const:`None`.
A clone is almost equivalent to deepcopy except that
any categorized objects encountered that are not
descendents of this object will reference the same
object on the clone.
"""
save_parent = self._parent
object.__setattr__(self, "_parent", None)
try:
new_block = copy.deepcopy(
self, {'__block_scope__': {id(self): True, id(None): False}}
)
finally:
object.__setattr__(self, "_parent", save_parent)
return new_block
#
# The following method must be defined to allow
# expressions and blocks to be correctly cloned.
# (copied from JDS code in original component.py,
# comments removed for now)
#
def __deepcopy__(self, memo):
if '__block_scope__' in memo:
_known = memo['__block_scope__']
_new = None
tmp = self.parent
_in_scope = tmp is None
while id(tmp) not in _known:
_new = (_new, id(tmp))
tmp = tmp.parent
_in_scope |= _known[id(tmp)]
while _new is not None:
_new, _id = _new
_known[_id] = _in_scope
if not _in_scope and id(self) not in _known:
# component is out-of-scope. shallow copy only
memo[id(self)] = self
return self
if id(self) in memo:
return memo[id(self)]
ans = memo[id(self)] = self.__class__.__new__(self.__class__)
ans.__setstate__(copy.deepcopy(self.__getstate__(), memo))
return ans
[docs]
class ICategorizedObjectContainer(ICategorizedObject):
"""
Interface for categorized containers of categorized
objects.
"""
_is_container = True
_child_storage_delimiter_string = None
_child_storage_entry_string = None
__slots__ = ()
#
# Interface
#
[docs]
def activate(self, shallow=True):
"""Activate this container."""
super(ICategorizedObjectContainer, self).activate()
if not shallow:
for child in self.children():
if not child._is_container:
child.activate()
else:
child.activate(shallow=False)
[docs]
def deactivate(self, shallow=True):
"""Deactivate this container."""
super(ICategorizedObjectContainer, self).deactivate()
if not shallow:
for child in self.children():
if not child._is_container:
child.deactivate()
else:
child.deactivate(shallow=False)
[docs]
def child(self, *args, **kwds):
"""Returns a child of this container given a storage
key."""
raise NotImplementedError # pragma:nocover
[docs]
def children(self, *args, **kwds):
"""A generator over the children of this container."""
raise NotImplementedError # pragma:nocover
[docs]
def components(self, *args, **kwds):
"""A generator over the set of components stored
under this container."""
raise NotImplementedError # pragma:nocover