# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 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 itertools
from pyomo.common import DeveloperError
from pyomo.common.collections import Sequence
from pyomo.core.base.enums import SortComponents
from pyomo.core.base.global_set import UnindexedComponent_index
[docs]
class IndexedComponent_slice:
"""Special class for slicing through hierarchical component trees
The basic concept is to interrupt the normal slice generation
procedure to return a specialized iterable class (this object). This
object supports simple getitem / getattr / call methods and caches
them until it is time to actually iterate through the slice. We
then walk down the cached names / indices and resolve the final
objects during the iteration process. This works because all the
calls to __getitem__ / __getattr__ / __call__ happen *before* the
call to __iter__()
"""
ATTR_MASK = 8
ITEM_MASK = 16
CALL_MASK = 32
GET_MASK = 1
SET_MASK = 2
DEL_MASK = 4
slice_info = 0
get_attribute = ATTR_MASK | GET_MASK
set_attribute = ATTR_MASK | SET_MASK
del_attribute = ATTR_MASK | DEL_MASK
get_item = ITEM_MASK | GET_MASK
set_item = ITEM_MASK | SET_MASK
del_item = ITEM_MASK | DEL_MASK
call = CALL_MASK
[docs]
def __init__(self, component, fixed=None, sliced=None, ellipsis=None):
"""A "slice" over an _IndexedComponent hierarchy
This class has two forms for the constructor. The first form is
the standard constructor that takes a base component and
indexing information. This form takes
IndexedComponent_slice(component, fixed, sliced, ellipsis)
The second form is a "copy constructor" that is used internally
when building up the "call stack" for the hierarchical slice. The
copy constructor takes an IndexedComponent_slice and an
optional "next term" in the slice construction (from get/set/del
item/attr or call):
IndexedComponent_slice(slice, next_term=None)
Parameters
----------
component: IndexedComponent
The base component for this slice
fixed: dict
A dictionary indicating the fixed indices of component,
mapping index position to value
sliced: dict
A dictionary indicating the sliced indices of component
mapping the index position to the (python) slice object
ellipsis: int
The position of the ellipsis in the initial component slice
"""
# Note that because we use a custom __setattr__, we need to
# define actual instance attributes using the base class
# __setattr__.
set_attr = super(IndexedComponent_slice, self).__setattr__
if type(component) is IndexedComponent_slice:
# Copy constructor
_len = component._len
# For efficiency, we will only duplicate the call stack
# list if this instance is not point to the end of the list.
if _len == len(component._call_stack):
set_attr('_call_stack', component._call_stack)
else:
set_attr('_call_stack', component._call_stack[:_len])
set_attr('_len', _len)
if fixed is not None:
self._call_stack.append(fixed)
self._len += 1
set_attr(
'call_errors_generate_exceptions',
component.call_errors_generate_exceptions,
)
set_attr(
'key_errors_generate_exceptions',
component.key_errors_generate_exceptions,
)
set_attr(
'attribute_errors_generate_exceptions',
component.attribute_errors_generate_exceptions,
)
else:
# Normal constructor
set_attr(
'_call_stack',
[
(
IndexedComponent_slice.slice_info,
(component, fixed, sliced, ellipsis),
)
],
)
set_attr('_len', 1)
# Since this is an object, users may change these flags
# between where they declare the slice and iterate over it.
set_attr('call_errors_generate_exceptions', True)
set_attr('key_errors_generate_exceptions', True)
set_attr('attribute_errors_generate_exceptions', True)
def __getstate__(self):
"""Serialize this object.
In general, we would not need to implement this (the object does
not leverage ``__slots__``). However, because we have a
"blanket" implementation of :py:meth:`__getattr__`, we need to
explicitly implement these to avoid "accidentally" extending or
evaluating this slice."""
return {k: getattr(self, k) for k in self.__dict__}
def __setstate__(self, state):
"""Deserialize the state into this object."""
set_attr = super(IndexedComponent_slice, self).__setattr__
for k, v in state.items():
set_attr(k, v)
def __deepcopy__(self, memo):
"""Deepcopy this object (leveraging :py:meth:`__getstate__`)"""
ans = memo[id(self)] = self.__class__.__new__(self.__class__)
ans.__setstate__(copy.deepcopy(self.__getstate__(), memo))
return ans
def __iter__(self):
"""Return an iterator over this slice"""
return _IndexedComponent_slice_iter(self)
def __getattr__(self, name):
"""Override the "." operator to defer resolution until iteration.
Creating a slice of a component returns a
IndexedComponent_slice object. Subsequent attempts to resolve
attributes hit this method.
"""
return IndexedComponent_slice(
self, (IndexedComponent_slice.get_attribute, name)
)
def __setattr__(self, name, value):
"""Override the "." operator implementing attribute assignment
This supports notation similar to:
del model.b[:].c.x = 5
and immediately evaluates the slice.
"""
# Don't overload any pre-existing attributes
if name in self.__dict__:
return super(IndexedComponent_slice, self).__setattr__(name, value)
# Immediately evaluate the slice and set the attributes
for i in IndexedComponent_slice(
self, (IndexedComponent_slice.set_attribute, name, value)
):
pass
return None
def __getitem__(self, idx):
"""Override the "[]" operator to defer resolution until iteration.
Creating a slice of a component returns a
IndexedComponent_slice object. Subsequent attempts to query
items hit this method.
"""
return IndexedComponent_slice(self, (IndexedComponent_slice.get_item, idx))
def __setitem__(self, idx, val):
"""Override the "[]" operator for setting item values.
This supports notation similar to:
model.b[:].c.x[1,:] = 5
and immediately evaluates the slice.
"""
# Immediately evaluate the slice and set the attributes
for i in IndexedComponent_slice(
self, (IndexedComponent_slice.set_item, idx, val)
):
pass
return None
def __delitem__(self, idx):
"""Override the "del []" operator for deleting item values.
This supports notation similar to:
del model.b[:].c.x[1,:]
and immediately evaluates the slice.
"""
# Immediately evaluate the slice and set the attributes
for i in IndexedComponent_slice(self, (IndexedComponent_slice.del_item, idx)):
pass
return None
def __call__(self, *args, **kwds):
"""Special handling of the "()" operator for component slices.
Creating a slice of a component returns a IndexedComponent_slice
object. Subsequent attempts to call items hit this method. We
handle the __call__ method separately based on the item (identifier
immediately before the "()") being called:
- if the item was 'component', then we defer resolution of this call
until we are actually iterating over the slice. This allows users
to do operations like `m.b[:].component('foo').bar[:]`
- if the item is anything else, then we will immediately iterate over
the slice and call the item. This allows "vector-like" operations
like: `m.x[:,1].fix(0)`.
"""
# There is a weird case in pypy3.6-7.2.0 where __name__ gets
# called after retrieving an attribute that will be called. I
# don't know why that happens, but we will trap it here and
# remove the getattr(__name__) from the call stack.
_len = self._len
if (
self._call_stack[_len - 1][0] == IndexedComponent_slice.get_attribute
and self._call_stack[_len - 1][1] == '__name__'
):
self._len -= 1
ans = IndexedComponent_slice(self, (IndexedComponent_slice.call, args, kwds))
# Because we just duplicated the slice and added a new entry, we
# know that the _len == len(_call_stack)
if ans._call_stack[-2][1] == 'component':
return ans
else:
# Note: simply calling "list(self)" results in infinite
# recursion in python2.6
return list(i for i in ans)
@classmethod
def _getitem_args_to_str(cls, args):
for i, v in enumerate(args):
if v is Ellipsis:
args[i] = '...'
elif type(v) is slice:
args[i] = (
(repr(v.start) if v.start is not None else '')
+ ':'
+ (repr(v.stop) if v.stop is not None else '')
+ (':%r' % v.step if v.step is not None else '')
)
else:
args[i] = repr(v)
return '[' + ', '.join(args) + ']'
def __str__(self):
ans = ''
for level in self._call_stack:
if level[0] == IndexedComponent_slice.slice_info:
ans += level[1][0].name
if level[1][1] is not None:
tmp = dict(level[1][1])
tmp.update(level[1][2])
if level[1][3] is not None:
tmp[level[1][3]] = Ellipsis
ans += self._getitem_args_to_str([tmp[i] for i in sorted(tmp)])
elif level[0] & IndexedComponent_slice.ITEM_MASK:
if isinstance(level[1], Sequence):
tmp = list(level[1])
else:
tmp = [level[1]]
ans += self._getitem_args_to_str(tmp)
elif level[0] & IndexedComponent_slice.ATTR_MASK:
ans += '.' + level[1]
elif level[0] & IndexedComponent_slice.CALL_MASK:
ans += (
'('
+ ', '.join(
itertools.chain(
(repr(_) for _ in level[1]),
('%s=%r' % kv for kv in level[2].items()),
)
)
+ ')'
)
if level[0] & IndexedComponent_slice.SET_MASK:
ans += ' = %r' % (level[2],)
elif level[0] & IndexedComponent_slice.DEL_MASK:
ans = 'del ' + ans
return ans
def __hash__(self):
return hash(tuple(_freeze(x) for x in self._call_stack[: self._len]))
def __eq__(self, other):
if other is self:
return True
if type(other) is not IndexedComponent_slice:
return False
return tuple(_freeze(x) for x in self._call_stack[: self._len]) == tuple(
_freeze(x) for x in other._call_stack[: other._len]
)
def __ne__(self, other):
return not self.__eq__(other)
def duplicate(self):
ans = IndexedComponent_slice(self)
ans._call_stack = ans._call_stack[: ans._len]
return ans
def index_wildcard_keys(self, sort):
_iter = _IndexedComponent_slice_iter(self, iter_over_index=True, sort=sort)
return (_iter.get_last_index_wildcards() for _ in _iter)
def wildcard_keys(self, sort=SortComponents.UNSORTED):
_iter = _IndexedComponent_slice_iter(self, sort=sort)
return (_iter.get_last_index_wildcards() for _ in _iter)
[docs]
def wildcard_values(self, sort=SortComponents.UNSORTED):
"""Return an iterator over this slice"""
return _IndexedComponent_slice_iter(self, sort=sort)
def wildcard_items(self, sort=SortComponents.UNSORTED):
_iter = _IndexedComponent_slice_iter(self, sort=sort)
return ((_iter.get_last_index_wildcards(), _) for _ in _iter)
def expanded_keys(self):
_iter = self.__iter__()
return (_iter.get_last_index() for _ in _iter)
def expanded_items(self):
_iter = self.__iter__()
return ((_iter.get_last_index(), _) for _ in _iter)
def _freeze(info):
if info[0] == IndexedComponent_slice.slice_info:
return (
info[0],
id(info[1][0]), # id of the Component
tuple(info[1][1].items()), # {idx: value} for fixed
tuple(info[1][2].keys()), # {idx: slice} for slices
info[1][3], # ellipsis index
)
elif info[0] & IndexedComponent_slice.ITEM_MASK:
if type(info[1]) is not tuple:
index = (info[1],)
else:
index = info[1]
return (
info[0],
tuple((x.start, x.stop, x.step) if type(x) is slice else x for x in index),
info[2:],
)
else:
return info
class _slice_generator:
"""Utility (iterator) for generating the elements of one slice
Iterate through the component index and yield the component data
values that match the slice template.
"""
def __init__(self, component, fixed, sliced, ellipsis, iter_over_index, sort):
self.component = component
self.fixed = fixed
self.sliced = sliced
self.ellipsis = ellipsis
self.iter_over_index = iter_over_index
# Cache for the most recent index returned. This is used to
# iterate over keys of the slice (for instance, in a
# _ReferenceDict).
self.last_index = ()
self.tuplize_unflattened_index = (
len(list(self.component.index_set().subsets())) <= 1
)
if fixed is None and sliced is None and ellipsis is None:
# This is a slice rooted at a concrete component. This is
# unusual (i.e., it is generated programmatically and not
# through the normal slice/ellipsis notation). We will mock
# up a "generator" so that the slice can be cleanly iterated
# over
self.explicit_index_count = 0
self.component_iter = _NotIterable
return
self.explicit_index_count = len(fixed) + len(sliced)
if iter_over_index and component.index_set().isfinite():
# This should be used to iterate over all the potential
# indices of a sparse IndexedComponent.
if SortComponents.SORTED_INDICES in sort:
self.component_iter = component.index_set().sorted_iter()
elif SortComponents.ORDERED_INDICES in sort:
self.component_iter = component.index_set().ordered_iter()
else:
self.component_iter = iter(component.index_set())
else:
# The default behavior is to iterate over the component.
self.component_iter = component.keys(sort)
def next(self):
"""__next__() iterator for Py2 compatibility"""
return self.__next__()
def __next__(self):
# We have to defer this import to here to resolve circular
# imports. Ideally, we would move normalize_index to another
# module to resolve this.
from .indexed_component import normalize_index
if self.component_iter is _NotIterable:
# Special case handling for "slices" rooted at concrete
# components. We will replace the iterator with an "empty"
# iterator so that the next call will raise StopIteration
self.component_iter = iter(())
return self.component
while 1:
# Note: running off the end of the underlying iterator will
# generate a StopIteration exception that will propagate up
# and end this iterator.
index = next(self.component_iter)
# We want a tuple of indices, so convert scalars to tuples
if normalize_index.flatten:
_idx = index if type(index) is tuple else (index,)
elif self.tuplize_unflattened_index:
_idx = (index,)
else:
_idx = index
# Verify the number of indices: if there is a wildcard
# slice, then there must be enough indices to at least match
# the fixed indices. Without the wildcard slice (ellipsis),
# the number of indices must match exactly.
if self.ellipsis is not None:
if self.explicit_index_count > len(_idx):
continue
elif len(_idx) != self.explicit_index_count:
continue
valid = True
for key, val in self.fixed.items():
# If this index of the component does not match all
# the specified fixed indices, don't return anything.
if not val == _idx[key]:
valid = False
break
if valid:
# Remember the index tuple corresponding to the last
# component data returned by this iterator. In this way
# we can use the cached indices to iterate over "indices"
# of a slice.
#
# last_index is the most recent index encountered, not
# the last index that will ever be encountered.
self.last_index = _idx
# Note: it is important to use __getitem__, as the
# derived class may implement a non-standard storage
# mechanism (e.g., Param)
if (not self.iter_over_index) or index in self.component:
# If iter_over_index is False, we are iterating over
# the component ("filled-in" indices only). Since
# `advance_iter` was called on the component iter,
# we already know index is in self.component.
return self.component[index]
else:
# If iter_over_index is True, we need to return
# something even when index is not actually in
# self.component. We will (arbitrarily) return
# None.
return None
# Backwards compatibility
_IndexedComponent_slice = IndexedComponent_slice
# Mock up a callable object with a "check_complete" method
def _advance_iter(_iter):
return next(_iter)
def _advance_iter_check_complete():
pass
_advance_iter.check_complete = _advance_iter_check_complete
# A dummy class that we can use as a named entity below
class _NotIterable:
pass
class _IndexedComponent_slice_iter:
def __init__(
self,
component_slice,
advance_iter=_advance_iter,
iter_over_index=False,
sort=False,
):
# _iter_stack holds a list of elements X where X is either a
# _slice_generator iterator (if this level in the hierarchy is a
# slice) or None (if this level is either a SimpleComponent,
# attribute, method, or is explicitly indexed).
self._slice = component_slice
self.advance_iter = advance_iter
self._iter_over_index = iter_over_index
self._sort = SortComponents(sort)
call_stack = self._slice._call_stack
call_stack_len = self._slice._len
self._iter_stack = [None] * call_stack_len
# Initialize the top of the `_iter_stack` (deepest part of the
# model hierarchy):
if call_stack[0][0] == IndexedComponent_slice.slice_info:
# The root of the _iter_stack is a generator for the
# "highest-level slice" (slice closest to the model() block)
self._iter_stack[0] = _slice_generator(
*call_stack[0][1],
iter_over_index=self._iter_over_index,
sort=self._sort,
)
# call_stack[0][1] is a (fixed, sliced, ellipsis) tuple, where
# fixed and sliced are dicts.
elif call_stack[0][0] == IndexedComponent_slice.set_item:
# This is a special case that happens when calling
# `_ReferenceDict.__setitem__` when the call stack consists
# of only a `set_item` entry. We need to initialize the
# root of _iter_stack to something other than None (so it
# doesn't immediately get "popped" off. However, set_item
# is not an iterable thing, so we will will use a type flag
# to signal this case to __next__below.
assert call_stack_len == 1
self._iter_stack[0] = _NotIterable # Something not None
else:
raise DeveloperError(
"Unexpected call_stack flag encountered: %s" % call_stack[0][0]
)
def __iter__(self):
"""This class implements the iterator API"""
return self
def next(self):
"""__next__() iterator for Py2 compatibility"""
return self.__next__()
def __next__(self):
"""Return the next element in the slice."""
# In each call to this function, idx will initially point
# to the bottom of the stack.
#
# NOTE: We refer to this stack as growing "downward", just like
# the model hierarchy to which it refers.
idx = len(self._iter_stack) - 1
while True:
# Flush out any non-slice levels. Since we initialize
# _iter_stack with None, in the first call this will
# immediately walk up to the beginning of the _iter_stack
#
# On subsequent calls, we will walk up only as far as the
# "deepest" active (non-exhausted) iterator. Higher-level
# iterators could still be active as well, but those index
# values will remain constant until we have exhausted this
# "deepest" iterator.
while self._iter_stack[idx] is None:
idx -= 1
# Get the next element in the deepest iterator (active slice)
try:
if self._iter_stack[idx] is _NotIterable:
# This happens when attempting a `set_item` call on
# a `_ReferenceDict` whose slice consists of only a
# `slice_info` entry.
# E.g.
# ref = Reference(m.x[:])
# ref._data[1] = 2
# but not
# ref = Reference(m.b[:].x[:])
# ref._data['a',1] = 2
#
_comp = self._slice._call_stack[0][1][0]
# _comp is the component in the slice_info entry
# of the call stack
else:
# Advance the "deepest active iterator"
_comp = self.advance_iter(self._iter_stack[idx])
# Note that if we are looking for a specific
# wildcard index, that data is stored in
# advance_iter() and will be automatically inserted.
#
# _comp is "local" part of the component we're
# looking for. The rest of the component will be
# located using the remainder of the iter stack.
#
# Note that _comp is actually a component data, because
# the _slice_generator (_iter_stack[idx]) returns
# component datas rather than indices.
# The _slice_generator is able to know about its
# component because it was created from a "higher-
# level" component/slice in the call/iter stack.
# A higher-level iterator may still be active, and
# this _slice_generator will need to be regenerated
# when/if that iterator is advanced.
idx += 1
except StopIteration:
# We have exhausted the iterator at this level of the
# stack
if not idx:
# Top-level iterator is done. We are done.
# (This is how the infinite loop terminates!)
raise
# Reset the _slice_generator to None so that the next
# iteration will walk up to - and advance - the
# "next-highest level" iterator.
self._iter_stack[idx] = None
# Trivial optimization: we now know that the
# _iter_stack[idx] is None, so we can preemptively
# decrement idx in preparatioon for the next iteration
# of this loop.
idx -= 1
continue
# Walk to the end of the iter/call stacks, constructing a
# component to return along the way. The _iter_stack for
# all of these levels from idx to the end of the list are
# known to be None at this point.
while idx < self._slice._len:
_call = self._slice._call_stack[idx]
if _call[0] == IndexedComponent_slice.get_attribute:
try:
# Attach attribute to our current component:
_comp = getattr(_comp, _call[1])
except AttributeError:
# Since we are slicing, we may only be interested in
# things that match. We will allow users to
# (silently) ignore any attribute errors generated
# by concrete indices in the slice hierarchy...
if (
self._slice.attribute_errors_generate_exceptions
and not self._iter_over_index
):
raise
# Break from the inner loop; next action will be to
# advance the "highest-level iterator"
break
elif _call[0] == IndexedComponent_slice.get_item:
try:
# Get the specified index for the current component:
_comp = _comp.__getitem__(_call[1])
except LookupError:
# Since we are slicing, we may only be
# interested in things that match. We will
# allow users to (silently) ignore any key
# errors generated by concrete indices in the
# slice hierarchy...
if (
self._slice.key_errors_generate_exceptions
and not self._iter_over_index
):
raise
break
# If the index defines a slice, add a slice generator
# to the iter_stack:
if _comp.__class__ is IndexedComponent_slice:
# Extract the _slice_generator (for
# efficiency... these are always 1-level slices,
# so we don't need the overhead of the
# IndexedComponent_slice object)
assert _comp._len == 1
self._iter_stack[idx] = _slice_generator(
*_comp._call_stack[0][1],
iter_over_index=self._iter_over_index,
sort=self._sort,
)
try:
# Advance to get the first component defined
# by this slice (so that we have a concrete
# context that we can use to descend further
# down the model hierarchy):
_comp = self.advance_iter(self._iter_stack[idx])
# Note that the iterator will remained
# cached for subsequent calls to __next__()
# (when it will eventually be exhausted).
except StopIteration:
# We got a slicer, but the slicer doesn't
# match anything. We should break here,
# which (due to 'while True' above) will
# walk back up to the next iterator and move
# on
self._iter_stack[idx] = None
break
else:
# `_comp` is a fully qualified component data (i.e.,
# not a slice). Record None in the _iter_stack
# so we note that this level in the stack is not
# defined by an iterator (i.e., subsequent calls
# should immediately "pop" this level off the
# stack and proceed to the next higher level.
self._iter_stack[idx] = None
elif _call[0] == IndexedComponent_slice.call:
try:
# Assume the callable "comp" in our hierarchy
# returns a component:
_comp = _comp(*(_call[1]), **(_call[2]))
except:
# Since we are slicing, we may only be
# interested in things that match. We will
# allow users to (silently) ignore any key
# errors generated by concrete indices in the
# slice hierarchy...
if (
self._slice.call_errors_generate_exceptions
and not self._iter_over_index
):
raise
break
elif _call[0] == IndexedComponent_slice.set_attribute:
# set_attribute should only appear at the deepest
# point (end) of the call stack
assert idx == self._slice._len - 1
try:
# set attribute of this component:
_comp = setattr(_comp, _call[1], _call[2])
# If we want to support __setattr__ with "vector"
# arguments, e.g.
# `m.b[:].v.value = [1,2,3]` or
# `m.b[:].v[1].value = m.b[:].v[0].value`,
# this will need to be modified to
# cache an iterator over _call[2].
except AttributeError:
# Since we are slicing, we may only be interested in
# things that match. We will allow users to
# (silently) ignore any attribute errors generated
# by concrete indices in the slice hierarchy...
if self._slice.attribute_errors_generate_exceptions:
raise
break
elif _call[0] == IndexedComponent_slice.set_item:
# `set_item` must always appear at the deepest
# point (end) of the call stack
assert idx == self._slice._len - 1
# We have a somewhat unusual situation when someone
# makes a _ReferenceDict to m.x[:] and then wants to
# set one of the items. In that situation,
# there is only one level in the _call_stack, and we
# need to iterate over it here (so we do not allow
# the outer portion of this loop to handle the
# iteration). This is indicated by setting the
# _iter_stack value to _NotIterable.
if self._iter_stack[idx] is _NotIterable:
_iter = _slice_generator(
*_call[1],
iter_over_index=self._iter_over_index,
sort=self._sort,
)
while True:
# This ends when the _slice_generator raises
# a StopIteration exception
self.advance_iter(_iter)
# Check to make sure the custom iterator
# (i.e._fill_in_known_wildcards) is complete
self.advance_iter.check_complete()
_comp[_iter.last_index] = _call[2]
# The problem here is that _call[1] may be a slice.
# If it is, but we are in something like a
# _ReferenceDict, where the caller actually wants a
# specific index from the slice, we cannot simply
# set every element of the slice. Instead, we will
# look for the component (generating a slice if
# appropriate). If it returns a slice, we can use
# our current advance_iter to walk it and set only
# the appropriate keys
try:
_tmp = _comp.__getitem__(_call[1])
except KeyError:
# Since we are slicing, we may only be
# interested in things that match. We will
# allow users to (silently) ignore any key
# errors generated by concrete indices in the
# slice hierarchy...
if (
self._slice.key_errors_generate_exceptions
and not self._iter_over_index
):
raise
break
if _tmp.__class__ is IndexedComponent_slice:
# Extract the _slice_generator and evaluate it.
assert _tmp._len == 1
_iter = _IndexedComponent_slice_iter(
_tmp, self.advance_iter, sort=self._sort
)
for _ in _iter:
# Check to make sure the custom iterator
# (i.e._fill_in_known_wildcards) is complete
self.advance_iter.check_complete()
_comp[_iter.get_last_index()] = _call[2]
break
else:
# Check to make sure the custom iterator
# (i.e._fill_in_known_wildcards) is complete
self.advance_iter.check_complete()
# No try-catch, since we know this key is valid
_comp[_call[1]] = _call[2]
# If we want to support vectorized set_item, e.g.
# `m.b[:].v[1] = m.b[:].v[0]`,
# we need to cache an iterator over _call[2].
elif _call[0] == IndexedComponent_slice.del_item:
assert idx == self._slice._len - 1
# The problem here is that _call[1] may be a slice.
# If it is, but we are in something like a
# _ReferenceDict, where the caller actually wants a
# specific index from the slice, we cannot simply
# delete the slice from the component. Instead, we
# will look for the component (generating a slice if
# appropriate). If it returns a slice, we can use
# our current advance_iter to walk it and delete the
# appropriate keys
try:
_tmp = _comp.__getitem__(_call[1])
except KeyError:
# Since we are slicing, we may only be
# interested in things that match. We will
# allow users to (silently) ignore any key
# errors generated by concrete indices in the
# slice hierarchy...
if self._slice.key_errors_generate_exceptions:
raise
break
if _tmp.__class__ is IndexedComponent_slice:
# Extract the _slice_generator and evaluate it.
assert _tmp._len == 1
_iter = _IndexedComponent_slice_iter(
_tmp, self.advance_iter, sort=self._sort
)
_idx_to_del = []
# Two passes, so that we don't edit the _data
# dicts while we are iterating over them
for _ in _iter:
_idx_to_del.append(_iter.get_last_index())
# Check to make sure the custom iterator
# (i.e._fill_in_known_wildcards) is complete
self.advance_iter.check_complete()
for _idx in _idx_to_del:
del _comp[_idx]
break
else:
# No try-catch, since we know this key is valid
del _comp[_call[1]]
elif _call[0] == IndexedComponent_slice.del_attribute:
assert idx == self._slice._len - 1
try:
_comp = delattr(_comp, _call[1])
except AttributeError:
# Since we are slicing, we may only be interested in
# things that match. We will allow users to
# (silently) ignore any attribute errors generated
# by concrete indices in the slice hierarchy...
if self._slice.attribute_errors_generate_exceptions:
raise
break
else:
raise DeveloperError(
"Unexpected entry in IndexedComponent_slice "
"_call_stack: %s" % (_call[0],)
)
idx += 1
if idx == self._slice._len:
# Check to make sure the custom iterator
# (i.e._fill_in_known_wildcards) is complete
self.advance_iter.check_complete()
# We have a concrete object at the end of the chain. Return it
return _comp
def get_last_index(self):
ans = sum((x.last_index for x in self._iter_stack if x is not None), ())
if len(ans) == 1:
return ans[0]
else:
return ans
def get_last_index_wildcards(self):
"""Get a tuple of the values in the wildcard positions for the most
recent indices corresponding to the last component returned by
each _slice_generator in the iter stack.
"""
# This method is how we iterate over keys.
#
# last_index is the index corresponding to the most recent
# component data returned by the corresponding _slice_generator.
# Extract the indices corresponding to the wildcard positions
# for that slice.
ans = sum(
(
tuple(
x.last_index[i]
for i in range(len(x.last_index))
if i not in x.fixed
)
for x in self._iter_stack
if x is not None
),
(),
)
if not ans:
return UnindexedComponent_index
if len(ans) == 1:
return ans[0]
else:
return ans