예제 #1
0
    def class_decorator(cls):
        assert (len(cls.__bases__) == 1)
        base = cls.__bases__[0]

        def construct(self, data=None):
            if hasattr(self,
                       '_name') and self._name == self.__class__.__name__:
                self._name = base.__name__
            self.__class__ = base
            return base.construct(self, data)

        construct.__doc__ = base.construct.__doc__
        cls.construct = construct

        for method in methods:
            if type(method) is tuple:
                method, msg = method
            else:
                msg = None
            if not hasattr(base, method):
                raise DeveloperError(
                    "Cannot disable method %s on %s: not present on base class"
                    % (method, cls))
            base_method = getattr(base, method)
            if type(base_method) is property:
                setattr(cls, method, _disable_property(base_method, msg))
            else:
                setattr(cls, method, _disable_method(base_method, msg))
        return cls
예제 #2
0
 def __init__(self, component_slice, advance_iter=_advance_iter,
              iter_over_index=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
     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)
         # 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])
예제 #3
0
 def __delitem__(self, key):
     tmp = self._slice.duplicate()
     op = tmp._call_stack[-1][0]
     if op == _IndexedComponent_slice.get_item:
         # If the last attribute of the slice gets an item,
         # change it to delete the item
         tmp._call_stack[-1] = (_IndexedComponent_slice.del_item,
                                tmp._call_stack[-1][1])
     elif op == _IndexedComponent_slice.slice_info:
         assert len(tmp._call_stack) == 1
         _iter = self._get_iter(tmp, key)
         try:
             advance_iterator(_iter)
             del _iter._iter_stack[0].component[_iter.get_last_index()]
             return
         except StopIteration:
             raise KeyError("KeyError: %s" % (key, ))
     elif op == _IndexedComponent_slice.get_attribute:
         # If the last attribute of the slice retrieves an attribute,
         # change it to delete the attribute
         tmp._call_stack[-1] = (_IndexedComponent_slice.del_attribute,
                                tmp._call_stack[-1][1])
     else:
         raise DeveloperError("Unexpected slice _call_stack operation: %s" %
                              op)
     try:
         advance_iterator(self._get_iter(tmp, key))
     except StopIteration:
         pass
예제 #4
0
 def __setitem__(self, key, val):
     tmp = self._slice.duplicate()
     op = tmp._call_stack[-1][0]
     # Replace the end of the duplicated slice's call stack (deepest
     # level of the component hierarchy) with the appropriate
     # `set_item` call.  Then implement the actual __setitem__ by
     # advancing the duplicated iterator.
     if op == IndexedComponent_slice.get_item:
         tmp._call_stack[-1] = (
             IndexedComponent_slice.set_item,
             tmp._call_stack[-1][1],
             val )
     elif op == IndexedComponent_slice.slice_info:
         tmp._call_stack[-1] = (
             IndexedComponent_slice.set_item,
             tmp._call_stack[-1][1],
             val )
     elif op == IndexedComponent_slice.get_attribute:
         tmp._call_stack[-1] = (
             IndexedComponent_slice.set_attribute,
             tmp._call_stack[-1][1],
             val )
     else:
         raise DeveloperError(
             "Unexpected slice _call_stack operation: %s" % op)
     try:
         advance_iterator(self._get_iter(tmp, key, get_if_not_present=True))
     except StopIteration:
         pass
예제 #5
0
 def clear(self):
     """Clear the data in this component"""
     if self.is_indexed():
         self._data = {}
     else:
         raise DeveloperError(
             "Derived scalar component %s failed to define clear()." %
             (self.__class__.__name__, ))
예제 #6
0
 def exitNode(self, node, values):
     """ Visit nodes that have been expanded """
     _sympyOp = node
     _op = _operatorMap.get(type(_sympyOp), None)
     if _op is None:
         raise DeveloperError(
             "sympy expression type '%s' not found in the operator "
             "map" % type(_sympyOp))
     return _op(*tuple(values))
예제 #7
0
 def set_value(self, value):
     """Set the value of a scalar component."""
     if self.is_indexed():
         raise ValueError(
             "Cannot set the value for the indexed component '%s' "
             "without specifying an index value.\n"
             "\tFor example, model.%s[i] = value" % (self.name, self.name))
     else:
         raise DeveloperError(
             "Derived component %s failed to define set_value() "
             "for scalar instances." % (self.__class__.__name__, ))
예제 #8
0
 def __init__(self, component_slice, advance_iter=_advance_iter):
     # _iter_stack holds a list of elements X where X is either an
     # _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
     call_stack = self._slice._call_stack
     self._iter_stack = [None]*len(call_stack)
     if call_stack[0][0] == _IndexedComponent_slice.slice_info:
         self._iter_stack[0] = _slice_generator(*call_stack[0][1])
     elif call_stack[0][0] == _IndexedComponent_slice.set_item:
         assert len(call_stack) == 1
         # defer creating the iterator until later
         self._iter_stack[0] = _NotIterable # Something not None
     else:
         raise DeveloperError("Unexpected call_stack flag encountered: %s"
                              % call_stack[0][0])
예제 #9
0
 def __setitem__(self, key, val):
     tmp = self._slice.duplicate()
     op = tmp._call_stack[-1][0]
     if op == _IndexedComponent_slice.get_item:
         tmp._call_stack[-1] = (_IndexedComponent_slice.set_item,
                                tmp._call_stack[-1][1], val)
     elif op == _IndexedComponent_slice.slice_info:
         tmp._call_stack[-1] = (_IndexedComponent_slice.set_item,
                                tmp._call_stack[-1][1], val)
     elif op == _IndexedComponent_slice.get_attribute:
         tmp._call_stack[-1] = (_IndexedComponent_slice.set_attribute,
                                tmp._call_stack[-1][1], val)
     else:
         raise DeveloperError("Unexpected slice _call_stack operation: %s" %
                              op)
     try:
         advance_iterator(self._get_iter(tmp, key, get_if_not_present=True))
     except StopIteration:
         pass
예제 #10
0
 def index(self):
     """
     Returns the index of this ComponentData instance relative
     to the parent component index set. None is returned if
     this instance does not have a parent component, or if
     - for some unknown reason - this instance does not belong
     to the parent component's index set.
     """
     parent = self.parent_component()
     if (parent is not None and self._index is not NOTSET
             and parent[self._index] is not self):
         # This error message is a bit goofy, but we can't call self.name
         # here--it's an infinite loop!
         raise DeveloperError(
             "The '_data' dictionary and '_index' attribute are out of "
             "sync for indexed %s '%s': The %s entry in the '_data' "
             "dictionary does not map back to this component data object." %
             (parent.ctype.__name__, parent.name, self._index))
     return self._index
예제 #11
0
 def __init__(self, **kwds):
     #
     # Get arguments
     #
     self._ctype = kwds.pop('ctype', None)
     self.doc = kwds.pop('doc', None)
     self._name = kwds.pop('name', str(type(self).__name__))
     if kwds:
         raise ValueError(
             "Unexpected keyword options found while constructing '%s':\n\t%s"
             % (type(self).__name__, ','.join(sorted(kwds.keys()))))
     #
     # Verify that ctype has been specified.
     #
     if self._ctype is None:
         raise DeveloperError(
             "Must specify a component type for class %s!" %
             (type(self).__name__, ))
     #
     self._constructed = False
     self._parent = None  # Must be a weakref
예제 #12
0
    def _processUnhashableIndex(self, idx, _exception=None):
        """Process a call to __getitem__ with unhashable elements

        There are three basic ways to get here:
          1) the index contains one or more slices or ellipsis
          2) the index contains an unhashable type (e.g., a Pyomo
             (Simple)Component
          3) the index contains an IndexTemplate
        """
        from pyomo.core.expr import current as EXPR
        #
        # Iterate through the index and look for slices and constant
        # components
        #
        fixed = {}
        sliced = {}
        ellipsis = None
        _found_numeric = False
        #
        # Setup the slice template (in fixed)
        #
        if type(idx) is tuple:
            # We would normally do "flatten()" here, but the current
            # (10/2016) implementation of flatten() is too aggressive:
            # it will attempt to expand *any* iterable, including
            # SimpleParam.
            idx = pyutilib.misc.flatten_tuple(idx)
        elif type(idx) is list:
            idx = pyutilib.misc.flatten_tuple(tuple(idx))
        else:
            idx = (idx, )

        for i, val in enumerate(idx):
            if type(val) is slice:
                if val.start is not None or val.stop is not None:
                    raise IndexError(
                        "Indexed components can only be indexed with simple "
                        "slices: start and stop values are not allowed.")
                if val.step is not None:
                    logger.warning(
                        "DEPRECATION WARNING: The special wildcard slice "
                        "(::0) is deprecated.  Please use an ellipsis (...) "
                        "to indicate '0 or more' indices")
                    val = Ellipsis
                else:
                    if ellipsis is None:
                        sliced[i] = val
                    else:
                        sliced[i - len(idx)] = val
                    continue

            if val is Ellipsis:
                if ellipsis is not None:
                    raise IndexError(
                        "Indexed components can only be indexed with simple "
                        "slices: the Pyomo wildcard slice (Ellipsis; "
                        "e.g., '...') can only appear once")
                ellipsis = i
                continue

            if hasattr(val, 'as_numeric'):
                try:
                    val = EXPR.evaluate_expression(val, constant=True)
                    _found_numeric = True

                except TemplateExpressionError:
                    #
                    # The index is a template expression, so return the
                    # templatized expression.
                    #
                    from pyomo.core.expr import current as EXPR
                    return EXPR.GetItemExpression(tuple(idx), self)

                except EXPR.NonConstantExpressionError:
                    #
                    # The expression contains an unfixed variable
                    #
                    raise RuntimeError(
                        """Error retrieving the value of an indexed item %s:
index %s is not a constant value.  This is likely not what you meant to
do, as if you later change the fixed value of the object this lookup
will not change.  If you understand the implications of using
non-constant values, you can get the current value of the object using
the value() function.""" % (self.name, i))

                except EXPR.FixedExpressionError:
                    #
                    # The expression contains a fixed variable
                    #
                    raise RuntimeError(
                        """Error retrieving the value of an indexed item %s:
index %s is a fixed but not constant value.  This is likely not what you
meant to do, as if you later change the fixed value of the object this
lookup will not change.  If you understand the implications of using
fixed but not constant values, you can get the current value using the
value() function.""" % (self.name, i))
                #
                # There are other ways we could get an exception such as
                # evaluating a Param / Var that is not initialized.
                # These exceptions will continue up the call stack.
                #

            # verify that the value is hashable
            hash(val)
            if ellipsis is None:
                fixed[i] = val
            else:
                fixed[i - len(idx)] = val

        if sliced or ellipsis is not None:
            return _IndexedComponent_slicer(self, fixed, sliced, ellipsis)
        elif _found_numeric:
            if len(idx) == 1:
                return fixed[0]
            else:
                return tuple(fixed[i] for i in range(len(idx)))
        elif _exception is not None:
            raise
        else:
            raise DeveloperError(
                "Unknown problem encountered when trying to retrieve "
                "index for component %s" % (self.name, ))
예제 #13
0
    def __next__(self):
        """Return the next element in the slice."""
        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
            while self._iter_stack[idx] is None:
                idx -= 1
            # Get the next element in the deepest active slice
            try:
                if self._iter_stack[idx] is _NotIterable:
                    _comp = self._slice._call_stack[0][1][0]
                else:
                    _comp = self.advance_iter(self._iter_stack[idx])
                    idx += 1
            except StopIteration:
                if not idx:
                    # Top-level iterator is done.  We are done.
                    # (This is how the infinite loop terminates!)
                    raise
                self._iter_stack[idx] = None
                idx -= 1
                continue
            # Walk down the hierarchy to get to the final object
            while idx < len(self._slice._call_stack):
                _call = self._slice._call_stack[idx]
                if _call[0] == _IndexedComponent_slice.get_attribute:
                    try:
                        _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
                elif _call[0] == _IndexedComponent_slice.get_item:
                    try:
                        _comp = _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 _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 len(_comp._call_stack) == 1
                        self._iter_stack[idx] = _slice_generator(
                            *_comp._call_stack[0][1],
                            iter_over_index=self._iter_over_index
                        )
                        try:
                            _comp = self.advance_iter(self._iter_stack[idx])
                        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:
                        self._iter_stack[idx] = None
                elif _call[0] == _IndexedComponent_slice.call:
                    try:
                        _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:
                    assert idx == len(self._slice._call_stack) - 1
                    try:
                        _comp = setattr(_comp, _call[1], _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:
                    assert idx == len(self._slice._call_stack) - 1
                    # We have a somewhat unusual situation when someone
                    # makes a _ReferenceDict to m.x[:] and then wants to
                    # set one of the attributes.  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
                        )
                        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 len(_tmp._call_stack) == 1
                        _iter = _IndexedComponent_slice_iter(
                            _tmp, self.advance_iter)
                        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]
                elif _call[0] == _IndexedComponent_slice.del_item:
                    assert idx == len(self._slice._call_stack) - 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 len(_tmp._call_stack) == 1
                        _iter = _IndexedComponent_slice_iter(
                            _tmp, self.advance_iter)
                        _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 == len(self._slice._call_stack) - 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 == len(self._slice._call_stack):
                # 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
예제 #14
0
    def _processUnhashableIndex(self, idx):
        """Process a call to __getitem__ with unhashable elements

        There are three basic ways to get here:
          1) the index contains one or more slices or ellipsis
          2) the index contains an unhashable type (e.g., a Pyomo
             (Simple)Component
          3) the index contains an IndexTemplate
        """
        from pyomo.core.expr import current as EXPR
        #
        # Iterate through the index and look for slices and constant
        # components
        #
        fixed = {}
        sliced = {}
        ellipsis = None
        _found_numeric = False
        #
        # Setup the slice template (in fixed)
        #
        if normalize_index.flatten:
            idx = normalize_index(idx)
        if idx.__class__ is not tuple:
            idx = (idx, )

        for i, val in enumerate(idx):
            if type(val) is slice:
                if val.start is not None or val.stop is not None:
                    raise IndexError(
                        "Indexed components can only be indexed with simple "
                        "slices: start and stop values are not allowed.")
                if val.step is not None:
                    logger.warning(
                        "DEPRECATION WARNING: The special wildcard slice "
                        "(::0) is deprecated.  Please use an ellipsis (...) "
                        "to indicate '0 or more' indices")
                    val = Ellipsis
                else:
                    if ellipsis is None:
                        sliced[i] = val
                    else:
                        sliced[i - len(idx)] = val
                    continue

            if val is Ellipsis:
                if ellipsis is not None:
                    raise IndexError(
                        "Indexed components can only be indexed with simple "
                        "slices: the Pyomo wildcard slice (Ellipsis; "
                        "e.g., '...') can only appear once")
                ellipsis = i
                continue

            if hasattr(val, 'is_expression_type'):
                _num_val = val
                # Attempt to retrieve the numeric value .. if this
                # is a template expression generation, then it
                # should raise a TemplateExpressionError
                try:
                    val = EXPR.evaluate_expression(val, constant=True)
                    _found_numeric = True

                except TemplateExpressionError:
                    #
                    # The index is a template expression, so return the
                    # templatized expression.
                    #
                    from pyomo.core.expr import current as EXPR
                    return EXPR.GetItemExpression((self, ) + tuple(idx))

                except EXPR.NonConstantExpressionError:
                    #
                    # The expression contains an unfixed variable
                    #
                    raise RuntimeError(
                        """Error retrieving the value of an indexed item %s:
index %s is not a constant value.  This is likely not what you meant to
do, as if you later change the fixed value of the object this lookup
will not change.  If you understand the implications of using
non-constant values, you can get the current value of the object using
the value() function.""" % (self.name, i))

                except EXPR.FixedExpressionError:
                    #
                    # The expression contains a fixed variable
                    #
                    raise RuntimeError(
                        """Error retrieving the value of an indexed item %s:
index %s is a fixed but not constant value.  This is likely not what you
meant to do, as if you later change the fixed value of the object this
lookup will not change.  If you understand the implications of using
fixed but not constant values, you can get the current value using the
value() function.""" % (self.name, i))
                #
                # There are other ways we could get an exception such as
                # evaluating a Param / Var that is not initialized.
                # These exceptions will continue up the call stack.
                #

            # verify that the value is hashable
            hash(val)
            if ellipsis is None:
                fixed[i] = val
            else:
                fixed[i - len(idx)] = val

        if sliced or ellipsis is not None:
            slice_dim = len(idx)
            if ellipsis is not None:
                slice_dim -= 1
            if normalize_index.flatten:
                set_dim = self.dim()
            elif self._implicit_subsets is None:
                # Scalar component.
                set_dim = 0
            else:
                set_dim = len(self._implicit_subsets)

            structurally_valid = False
            if slice_dim == set_dim or set_dim is None:
                structurally_valid = True
            elif ellipsis is not None and slice_dim < set_dim:
                structurally_valid = True
            elif set_dim == 0 and idx == (slice(None), ):
                # If dim == 0 and idx is slice(None), the component was
                # a scalar passed a single slice. Since scalar components
                # can be accessed with a "1-dimensional" index of None,
                # this behavior is allowed.
                #
                # Note that x[...] is caught above, as slice_dim will be
                # 0 in that case
                structurally_valid = True

            if not structurally_valid:
                raise IndexError(
                    "Index %s contains an invalid number of entries for "
                    "component %s. Expected %s, got %s." %
                    (idx, self.name, set_dim, slice_dim))
            return IndexedComponent_slice(self, fixed, sliced, ellipsis)
        elif _found_numeric:
            if len(idx) == 1:
                return fixed[0]
            else:
                return tuple(fixed[i] for i in range(len(idx)))
        else:
            raise DeveloperError(
                "Unknown problem encountered when trying to retrieve "
                "index for component %s" % (self.name, ))
예제 #15
0
    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
                        )
                        try:
                            # Advance to get the first component defined
                            # by this slice (so that we have a concrete
                            # context that we can use to decend 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
                        )
                        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)
                        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)
                        _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
예제 #16
0
    def _processUnhashableIndex(self, idx, _exception=None):
        """Process a call to __getitem__ with unhashable elements

        There are three basic ways to get here:
          1) the index contains one or more slices or ellipsis
          2) the index contains an unhashable type (e.g., a Pyomo
             (Simple)Component
          3) the index contains an IndexTemplate
        """
        #
        # Iterate through the index and look for slices and constant
        # components
        #
        fixed = {}
        sliced = {}
        ellipsis = None
        _found_numeric = False
        #
        # Setup the slice template (in fixed)
        #
        if type(idx) is tuple:
            # We would normally do "flatten()" here, but the current
            # (10/2016) implementation of flatten() is too aggressive:
            # it will attempt to expand *any* iterable, including
            # SimpleParam.
            idx = pyutilib.misc.flatten_tuple(idx)
        elif type(idx) is list:
            idx = pyutilib.misc.flatten_tuple(tuple(idx))
        else:
            idx = (idx, )

        for i, val in enumerate(idx):
            if type(val) is slice:
                if val.start is not None or val.stop is not None:
                    raise IndexError(
                        "Indexed components can only be indexed with simple "
                        "slices: start and stop values are not allowed.")
                if val.step is not None:
                    logger.warning(
                        "DEPRECATION WARNING: The special wildcard slice "
                        "(::0) is deprecated.  Please use an ellipsis (...) "
                        "to indicate '0 or more' indices")
                    val = Ellipsis
                else:
                    if ellipsis is None:
                        sliced[i] = val
                    else:
                        sliced[i - len(idx)] = val
                    continue

            if val is Ellipsis:
                if ellipsis is not None:
                    raise IndexError(
                        "Indexed components can only be indexed with simple "
                        "slices: the Pyomo wildcard slice (Ellipsis; "
                        "e.g., '...') can only appear once")
                ellipsis = i
                continue

            if hasattr(val, 'as_numeric'):
                _num_val = val.as_numeric()
                # Attempt to retrieve the numeric value .. if this
                # is a template expression generation, then it
                # should raise a TemplateExpressionError
                try:
                    # Disable all logging for the time being.  We are
                    # not keeping the result of this calculation - only
                    # seeing if it is possible.  Any errors generated
                    # evaluating the expression are not informative to
                    # the user
                    logging.disable(logging.CRITICAL)
                    _num_val()
                except TemplateExpressionError:
                    # Not good: we have to defer this import to now
                    # due to circular imports (expr imports _VarData
                    # imports indexed_component, but we need expr
                    # here
                    from pyomo.core.expr import current as EXPR
                    return EXPR.GetItemExpression(tuple(idx), self)
                except:
                    # There are other ways we could get an exception
                    # that is not TemplateExpressionError; most notably,
                    # evaluating a Param / Var that is not initialized.
                    # At this point, we will silently eat that
                    # error... it will come back again below.
                    pass
                finally:
                    logging.disable(logging.NOTSET)

                if _num_val.is_constant():
                    _found_numeric = True
                    val = _num_val()
                elif _num_val.is_fixed():
                    raise RuntimeError(
                        """Error retrieving the value of an indexed item %s:
index %s is a fixed but not constant value.  This is likely not what you
meant to do, as if you later change the fixed value of the object this
lookup will not change.  If you understand the implications of using
fixed but not constant values, you can get the current value using the
value() function.""" % (self.name, i))
                else:
                    raise RuntimeError(
                        """Error retrieving the value of an indexed item %s:
index %s is not a constant value.  This is likely not what you meant to
do, as if you later change the fixed value of the object this lookup
will not change.  If you understand the implications of using
non-constant values, you can get the current value of the object using
the value() function.""" % (self.name, i))
            # verify that the value is hashable
            hash(val)
            if ellipsis is None:
                fixed[i] = val
            else:
                fixed[i - len(idx)] = val

        if sliced or ellipsis is not None:
            return _IndexedComponent_slicer(self, fixed, sliced, ellipsis)
        elif _found_numeric:
            if len(idx) == 1:
                return fixed[0]
            else:
                return tuple(fixed[i] for i in range(len(idx)))
        elif _exception is not None:
            raise
        else:
            raise DeveloperError(
                "Unknown problem encountered when trying to retrieve "
                "index for component %s" % (self.name, ))