# ___________________________________________________________________________
#
# 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.
# ___________________________________________________________________________
from collections.abc import MutableSet as collections_MutableSet
from collections.abc import Set as collections_Set
from pyomo.common.autoslots import AutoSlots
from pyomo.common.collections.component_map import _hasher
def _rehash_keys(encode, val):
if encode:
# TBD [JDS 2/2024]: if we
#
# return list(val.values())
#
# here, then we get a strange failure when deepcopying
# ComponentSets containing an _ImplicitAny domain. We could
# track it down to the implementation of
# autoslots.fast_deepcopy, but couldn't find an obvious bug.
# There is no error if we just return the original dict, or if
# we return a tuple(val.values)
return val
else:
# object id() may have changed after unpickling,
# so we rebuild the dictionary keys
return {_hasher[obj.__class__](obj): obj for obj in val.values()}
[docs]
class ComponentSet(AutoSlots.Mixin, collections_MutableSet):
"""
This class is a replacement for set that allows Pyomo
modeling components to be used as entries. The
underlying hash is based on the Python id() of the
object, which gets around the problem of hashing
subclasses of NumericValue. This class is meant for
creating sets of Pyomo components. The use of non-Pyomo
components as entries should be avoided (as the behavior
is undefined).
References to objects are kept around as long as they
are entries in the container, so there is no need to
worry about id() clashes.
We also override __setstate__ so that we can rebuild the
container based on possibly updated object ids after
a deepcopy or pickle.
*** An instance of this class should never be
deepcopied/pickled unless it is done so along with
its component entries (e.g., as part of a block). ***
"""
__slots__ = ("_data",)
__autoslot_mappers__ = {'_data': _rehash_keys}
# Expose a "public" interface to the global _hasher dict
hasher = _hasher
[docs]
def __init__(self, iterable=None):
# maps id_hash(obj) -> obj
self._data = {}
if iterable is not None:
self.update(iterable)
def __str__(self):
"""String representation of the mapping."""
tmp = [f"{v} (key={k})" for k, v in self._data.items()]
return f"ComponentSet({tmp})"
[docs]
def update(self, iterable):
"""Update a set with the union of itself and others."""
if isinstance(iterable, ComponentSet):
self._data.update(iterable._data)
else:
self._data.update((_hasher[val.__class__](val), val) for val in iterable)
#
# Implement MutableSet abstract methods
#
def __contains__(self, val):
return _hasher[val.__class__](val) in self._data
def __iter__(self):
return iter(self._data.values())
def __len__(self):
return self._data.__len__()
[docs]
def add(self, val):
"""Add an element."""
self._data[_hasher[val.__class__](val)] = val
[docs]
def discard(self, val):
"""Remove an element. Do not raise an exception if absent."""
_id = _hasher[val.__class__](val)
if _id in self._data:
del self._data[_id]
#
# Overload MutableSet default implementations
#
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, collections_Set):
return False
return len(self) == len(other) and all(
_hasher[val.__class__](val) in self._data for val in other
)
def __ne__(self, other):
return not (self == other)
#
# The remaining MutableSet methods have slow default
# implementations.
#
[docs]
def clear(self):
"""Remove all elements from this set."""
self._data.clear()
[docs]
def remove(self, val):
"""Remove an element. If not a member, raise a KeyError."""
try:
del self._data[_hasher[val.__class__](val)]
except KeyError:
_id = _hasher[val.__class__](val)
raise KeyError(f"{val} (key={_id})") from None