示例#1
0
 def coord2param(coord):
     if subbed_index == iterParam:
         # Direct indexing that does not need to be inverted:
         return coord
     # We must subtract by the 'shift' that the index
     # adds to the parameter in order to invert from
     # the coordinate back to the corresponding parameter:
     param = dist_subtract(coord, shift)
     param = _simplifiedCoord(param, assumptions, requirements)
     return param
示例#2
0
    def _iterSubParamVals(self,
                          axis,
                          iterParam,
                          startArg,
                          endArg,
                          exprMap,
                          relabelMap=None,
                          reservedVars=None,
                          assumptions=USE_DEFAULTS,
                          requirements=None):
        '''
        Consider a substitution over a containing iteration (Iter) 
        defined via exprMap, relabelMap, etc, and expand the iteration 
        by substituting the "iteration parameter" over the range from 
        the "starting argument" to the "ending argument" 
        (both inclusive as provided).
        
        When the Indexed variable is substituted with a Composite, any 
        containing Iteration is to be expanded over the iteration range.
        This method returns a list of parameter values that covers 
        occupied portions of the full range in a manner that keeps 
        different inner iterations separate.  In particular, the 
        iteration range is broken up for the different Iter entries that
        are contained in this Composite.  If it is not substituted with 
        a composite, _NoExpandedIteration is raised.
        
        Requirements that are passed back ensure that substituted composites are
        valid (with iterations that have natural number extents), that the 
        start and end indices are within range and at integer positions,
        and also includes equalities for employed simplifications or inversions
        (translating from index coordinates to parameters).
        '''
        from .composite import Composite, IndexingError, _simplifiedCoord, \
                                  _generateCoordOrderAssumptions
        from .expr_tuple import ExprTuple
        from .expr_array import ExprArray
        from proveit.logic import Equals, InSet
        from proveit.number import GreaterEq, LessEq, Add, one, num, \
                                      dist_add, dist_subtract, Naturals
        from proveit._core_.expression.expr import _NoExpandedIteration
        from .iteration import Iter, InvalidIterationError

        if requirements is None: requirements = []

        subbed_var = self.var.substituted(exprMap, relabelMap, reservedVars,
                                          assumptions, requirements)
        index = self.indices[axis]
        subbed_index = index.substituted(exprMap, relabelMap, reservedVars,
                                         assumptions, requirements)

        if not isinstance(subbed_var, Composite) or \
                iterParam not in subbed_index.freeVars():
            # No expansion for this parameter here:
            raise _NoExpandedIteration()  # no expansionn

        # We cannot substitute in a Composite without doing an
        # iteration over it.  Only certain iterations are allowed
        # in this manner however.

        if subbed_index != iterParam:
            # The index isn't simply the parameter.  It has a shift.
            # find the shift.
            if not isinstance(subbed_index, Add):
                raise InvalidIterationError(subbed_index, iterParam)
            shift_terms = [
                term for term in subbed_index.operands if term != iterParam
            ]
            if len(shift_terms) == 1:
                shift = shift_terms[0]  # shift by a single term
            else:
                shift = dist_add(*shift_terms)  # shift by multple terms

        start_index = subbed_index.substituted({iterParam: startArg})
        end_index = subbed_index.substituted({iterParam: endArg})
        entry_span_requirements = []
        coord_simp_requirements = []

        if isinstance(subbed_var, ExprTuple):
            coords = subbed_var.entryCoords(self.base, assumptions,
                                            entry_span_requirements,
                                            coord_simp_requirements)
        else:
            if not isinstance(subbed_var, ExprArray):
                subbed_var_class_str = str(subbed_var.__class__)
                raise TypeError("Indexed variable should only be "
                                "substituted with ExprTuple or "
                                "ExprArray, not %s" % subbed_var_class_str)
            coords = subbed_var.entryCoords(self.base, axis, assumptions,
                                            entry_span_requirements,
                                            coord_simp_requirements)
        assert coords[0] == num(self.base)
        coord_order_assumptions = list(_generateCoordOrderAssumptions(coords))
        #print("indexed sub coords", self, assumptions, start_index, coords, end_index)
        extended_assumptions = assumptions + coord_order_assumptions

        # We will include all of the "entry span" requirements
        # ensuring that the ExprTuple or ExprArray is valid
        # (the length of iterations is a natural number).
        # We will only include coordinate simplification
        # requirements up to the last needed coordinate.
        requirements.extend(entry_span_requirements)

        # Find where the start index and end index belongs
        # relative to the entry coordinates.

        # The start is inclusive and is typically expected to
        # toward the beginning of the coordinates.
        # We'll get the first and the last insertion points w.r.t.
        # all coordinates equivalent to start_index.
        # The "first" insertion point may help determine if we have
        # an empty range case.  Otherwise, we use the "last"
        # insertion point so we are not including multiple equivalent
        # parameter values at the start.
        start_pos_firstlast = \
            LessEq.insertion_point(coords, start_index,
                                    equiv_group_pos = 'first&last',
                                    assumptions=extended_assumptions)
        # Check the start for an out of bounds error.
        start_pos = start_pos_firstlast[1]
        if start_pos == 0:
            msg = ("ExprTuple index out of range: %s not proven "
                   "to be >= %s (the base) when assuming %s" %
                   (str(start_index), str(coords[0]), str(assumptions)))
            raise IndexError(msg)

        # The end position splits into two cases.  In the simple case,
        # it lands at a singular entry as the last entry.  Otherwise,
        # we need to add one to the endArg to ensure we get past any
        # iterations that may or may not be empty and find the
        # insertion point.
        end_pos = None
        if end_index in coords:
            # Might be the simple case of a singular entry as the last
            # entry.
            end_pos = coords.index(end_index)
            end_coord = coords[end_pos]
            if isinstance(end_coord, Iter):
                # Not the simple case -- an iteration rather than
                end_pos = None  # a singular entry.
            else:
                # Check the end for an out of bounds error.
                if end_pos == len(coords) - 1:
                    msg = ("ExprTuple index out of range: %s not proven "
                           "to be < %s when assuming %s" %
                           (str(end_index), str(coords[-1]), str(assumptions)))
                    raise IndexError(msg)
                if start_pos_firstlast[0] > end_pos:
                    # Empty range (if valid at all).  Handle this
                    # at the Iter.substituted level.
                    raise EmptyIterException()

        if end_pos is None:
            # Not the simple case.  We need to add one to the endArg
            # to ensure we get past any iterations that may or may not
            # be empty.
            endArg = _simplifiedCoord(dist_add(endArg, one), assumptions,
                                      requirements)
            end_index = subbed_index.substituted({iterParam: endArg})
            # We would typically expect the end-index to come near the
            # end of the coordinates in which case it is more efficient
            # to search for the insertion point in reverse order, so use
            # Greater instead of Less.
            # Use the "last" insertion point for the start so we are not
            # including multiple equivalent parameter values at the
            # end.
            end_pos_from_end = \
                GreaterEq.insertion_point(list(reversed(coords)), end_index,
                                          equiv_group_pos = 'last',
                                          assumptions=extended_assumptions)
            # Check the end for an out of bounds error.
            if end_pos_from_end == 0:
                msg = ("ExprTuple index out of range: %s not proven "
                       "to be <= %s when assuming %s." %
                       (str(end_index), str(coords[-1]), str(assumptions)))
                raise IndexError(msg)
            end_pos = len(coords) - end_pos_from_end
            # Check to see if the range is empty.
            # Note: when start_pos==end_pos is the case when both
            # are within the same entry.
            if start_pos > end_pos:
                # Empty range (if valid at all).  Handle this
                # at the Iter.substituted level.
                raise EmptyIterException()

        # Include coordinate simplification requirements up to
        # the last used coordinate.
        coord_simp_req_map = {eq.rhs: eq for eq in coord_simp_requirements}
        for coord in coords[:end_pos + 1]:
            if coord in coord_simp_req_map:
                requirements.append(coord_simp_req_map[coord])

        # End-point requirements may be needed.
        for coord_and_endpoint in [(coords[start_pos - 1], start_index),
                                   (end_index, coords[end_pos])]:
            if coord_and_endpoint[0] == coord_and_endpoint[1]:
                # When the endpoint index is the same as the
                # coordinate, we don't need to add a requirement.
                continue
            try:
                # See if we simply need to prove an equality between
                # the endpoint index and the coordinate.
                eq = Equals(*coord_and_endpoint)
                eq.prove(assumptions, automation=False)
                requirements.append(eq)
            except ProofFailure:
                # Otherwise, we must prove that the difference
                # between the coordinate and endpoint
                # is in the set of natural numbers (integral and
                # in the correct order).
                requirement = \
                    InSet(dist_subtract(*reversed(coord_and_endpoint)),
                          Naturals)
                # Knowing the simplification may help prove the
                # requirement.
                _simplifiedCoord(requirement, assumptions, [])
                requirements.append(requirement.prove(assumptions))

        # We must put each coordinate in terms of iter parameter
        # values (arguments) via inverting the subbed_index.
        def coord2param(coord):
            if subbed_index == iterParam:
                # Direct indexing that does not need to be inverted:
                return coord
            # We must subtract by the 'shift' that the index
            # adds to the parameter in order to invert from
            # the coordinate back to the corresponding parameter:
            param = dist_subtract(coord, shift)
            param = _simplifiedCoord(param, assumptions, requirements)
            return param

        coord_params = [
            coord2param(coord) for coord in coords[start_pos:end_pos]
        ]

        # If the start and end are the same expression or known to
        # be equal, just return [startArg].
        if startArg == endArg:
            return [startArg]
        try:
            eq = Equals(startArg, endArg)
            requirement = eq.prove(assumptions, automation=False)
            requirements.append(requirement)
            return [startArg]
        except ProofFailure:
            # Return the start, end, and coordinates at the
            # start of entries in between.
            return [startArg] + coord_params + [endArg]
示例#3
0
    def entryCoords(self,
                    base,
                    assumptions=USE_DEFAULTS,
                    entry_span_requirements=None,
                    coord_simp_requirements=None):
        '''
        Return the simplified expressions for the coordinates of each
        entry of this ExprTuple in the proper order.  For each iteration
        entry (Iter), subsequent coordinates will account for the extent
        of that iteration.  The last coordinate is the length of the
        tuple + the base, including the extent of each iteration.
        These simplified coordinate expressions
        will be remembered and reused when a query is repeated.
        
        Appends to entry_span_requirements the requirements that ensure
        that iterations have a length that is a natural number.
        Appends to coord_simp_requirements the simplification
        equations for each coordinate.
        '''
        from proveit.logic import InSet
        from proveit.number import one, num, Naturals, \
            dist_add, dist_subtract
        from .iteration import Iter

        if entry_span_requirements is None: entry_span_requirements = []
        if coord_simp_requirements is None: coord_simp_requirements = []

        # Check to see if this was the same query as last time.
        # If so, reuse the last result.
        if self._lastEntryCoordInfo is not None:
            last_base, last_assumptions, last_span_requirements, \
            last_simp_requirements, last_coords, _ \
                = self._lastEntryCoordInfo
            if (last_base, last_assumptions) == (base, assumptions):
                # Reuse the previous result, including the requirements.
                entry_span_requirements.extend(last_span_requirements)
                coord_simp_requirements.extend(last_simp_requirements)
                return last_coords

        # Generate the coordinate list.
        coords = []
        new_span_requirements = []
        new_simp_requirements = []
        coord = num(base)
        for k, entry in enumerate(self):
            coords.append(coord)
            if isinstance(entry, Iter):
                entry_delta = _simplifiedCoord(
                    dist_subtract(entry.end_index, entry.start_index),
                    assumptions, new_span_requirements)
                # Add one, to get to the start of the next entry, and simplify.
                entry_span = _simplifiedCoord(dist_add(entry_delta,
                                                       one), assumptions,
                                              new_span_requirements)
                # From one entry to the next should be a natural number (could be
                # an empty entry).
                #print("simplified entry span", entry_span)
                new_span_requirements.append(
                    InSet(entry_span, Naturals).prove(assumptions))
                coord = _simplifiedCoord(dist_add(coord, entry_span),
                                         assumptions, new_simp_requirements)
            else:
                coord = _simplifiedCoord(dist_add(coord, one), assumptions,
                                         new_simp_requirements)
        # The last included 'coordinate' is one past the last
        # coordinate within the tuple range.  This value minus the base
        # is the length of the tuple, the number of elements it
        # conceptually contains.
        coords.append(coord)

        # Remember this result for next time in case the query is
        # repeated.
        coords_to_indices = {coord: i for i, coord in enumerate(coords)}
        coord_info = (base, assumptions, new_span_requirements, \
                      new_simp_requirements, coords, coords_to_indices)
        self._lastEntryCoordInfo = coord_info
        entry_span_requirements.extend(new_span_requirements)
        coord_simp_requirements.extend(new_simp_requirements)

        # Return the coordinate list.
        return coords
示例#4
0
    def getElem(self,
                coord,
                base=1,
                hint_idx=None,
                assumptions=USE_DEFAULTS,
                requirements=None):
        '''
        Return the tuple element at the coordinate, given as an 
        Expression, using the given assumptions as needed to interpret 
        the location indicated by this expression.  Required truths, 
        proven under the given assumptions, that  were used to make this
        interpretation will be appended to the given 'requirements' 
        (if provided).
        If a hint_idx is provided, use it as a starting entry
        index from which to search for the coordinate.  Otherwise,
        use the previously queried entry as the 'hint'.
        '''
        from .composite import _generateCoordOrderAssumptions, \
            _simplifiedCoord
        from proveit.number import num, Naturals, Less, LessEq, \
            dist_add, Neg, dist_subtract
        from proveit.logic import Equals, InSet
        from proveit.relation import TransitivityException
        from .iteration import Iter

        if len(self) == 0:
            raise ValueError("An empty ExprTuple has no elements to get")

        if requirements is None:
            requirements = [
            ]  # create the requirements list, but it won't be used

        nentries = len(self.entries)

        # First handle the likely case that the coordinate of the
        # element is just the starting coordinate of an entry.
        coord_to_idx = self.entryCoordToIndex(base, assumptions, requirements)

        coord = _simplifiedCoord(coord, assumptions, requirements)
        if coord in coord_to_idx:
            # Found the coordinate as the start of an entry.
            start_idx = coord_to_idx[coord]
            entry = self.entries[start_idx]
            if not isinstance(entry, Iter):
                self._lastQueriedEntryIndex = start_idx
                return entry  # just a normal entry
            # If this is an iteration entry, we need to be careful.
            # Ostensibly, we would want to return entry.first() but we
            # need to be make sure it is not an empty iteration.
            # Instead, we'll treat it like the "hard" case starting
            # from start_idx.
        elif hint_idx is not None:
            # Use the provided hint as the starting point entry
            # index.
            start_idx = hint_idx
        else:
            # We use the last queried index as the starting point
            # to make typical use-cases more efficient.
            start_idx = self._lastQueriedEntryIndex

        try:
            # First we need to find an entry whose starting coordinate
            # is at or beyond our desired 'coord'.  Search starting
            # from the "hint".
            coord_simp_requirements = []
            coords = self.entryCoords(base, assumptions, requirements,
                                      coord_simp_requirements)
            coord_order_assumptions = \
                list(_generateCoordOrderAssumptions(coords))
            extended_assumptions = assumptions + coord_order_assumptions

            # Record relations between the given 'coord' and each
            # entry coordinate in case we want to reuse it.'
            relations = [None] * (nentries + 1)

            # Search for the right 'idx' of the entry starting
            # from start_idx and going forward until we have gone
            # too far.
            for idx in range(start_idx, nentries + 1):
                # Check if 'coord' is less than coords[idx]
                #print("sort", coord, coords[idx], assumptions)
                relation = LessEq.sort([coord, coords[idx]],
                                       assumptions=extended_assumptions)
                relations[idx] = relation
                rel_first, rel_op = relation.operands[0], relation.operator
                if rel_first == coord and rel_op == Less._operator_:
                    break
                elif idx == nentries:
                    raise IndexError("Coordinate %s past the range of "
                                     "the ExprTuple, %s" %
                                     (str(coord), str(self)))

            # Now go back to an entry whose starting coordinate is less
            # than or equal to the desired 'coord'.
            while idx > 0:
                idx -= 1
                try:
                    # Try to prove coords[idx] <= coord.
                    relation = LessEq.sort([coords[idx], coord],
                                           assumptions=extended_assumptions,
                                           reorder=False)
                    relations[idx] = relation
                    break
                except TransitivityException:
                    # Since we could not prove that
                    # coords[idx] <= coord, we must prove
                    # coord < coords[idx] and keep going back.
                    relation = Less(coord,
                                    coords[idx]).prove(extended_assumptions)
                    relations[idx] = relation
                    continue

            # We have the right index.  Include coordinate
            # simplifications up to that point as requirements.
            coord_simp_req_map = {eq.rhs: eq for eq in coord_simp_requirements}
            for prev_coord in coords[:idx + 1]:
                if prev_coord in coord_simp_req_map:
                    requirements.append(coord_simp_req_map[prev_coord])

            # The 'coord' is within this particular entry.
            # Record the required relations that prove that.
            self._lastQueriedEntryIndex = idx
            requirements.append(relations[idx])
            requirements.append(relations[idx + 1])

            # And return the appropriate element within the
            # entry.
            entry = self.entries[idx]
            if relations[idx].operator == Equals._operator_:
                # Special case -- coord at the entry origin.
                if isinstance(entry, Iter):
                    return entry.first()
                else:
                    return entry

            # The entry must be an iteration.
            if not isinstance(entry, Iter):
                raise ExprTupleError("Invalid coordinate, %s, in "
                                     "ExprTuple, %s." %
                                     (str(coord), str(self)))

            # Make sure the coordinate is valid and not "in between"
            # coordinates at unit intervals.
            valid_coord = InSet(dist_subtract(coord, coords[idx]), Naturals)
            requirements.append(valid_coord.prove(assumptions))

            # Get the appropriate element within the iteration.
            iter_start_index = entry.start_index
            iter_loc = dist_add(iter_start_index,
                                dist_subtract(coord, coords[idx]))
            simplified_iter_loc = _simplifiedCoord(iter_loc, assumptions,
                                                   requirements)
            # Does the same as 'entry.getInstance' but without checking
            # requirements; we don't need to worry about these requirements
            # because we already satisfied the requirements that we need.
            return entry.lambda_map.mapped(simplified_iter_loc)

        except ProofFailure as e:
            msg = ("Could not determine the element at "
                   "%s of the ExprTuple %s under assumptions %s." %
                   (str(coord), str(self), str(e.assumptions)))
            raise ExprTupleError(msg)

        raise IndexError("Unable to prove that "
                         "%s > %d to be within ExprTuple %s." %
                         (str(coord), base, str(self)))
示例#5
0
 def __init__(self, *expressions):
     '''
     Initialize an ExprTuple from an iterable over Expression 
     objects.  When subsequent iterations in the tuple form a
     self-evident continuation, these iterations will be joined.
     For example, (a_1, ..., a_n, a_{n+1}, ..., a_m) will join to
     form (a_1, ..., a_m).  "Self-evident" falls under two 
     categories: the start of the second iteration is the
     successor to the end of the first iteration (e.g., n and n+1)
     or the end of the first iteration is the predecessor of the
     start of the second iteration (e.g., n-1 and n).  To be a
     valid ExprTuple, all iterations must span a range whose
     extent is a natural number.  In the above example with the
     tuple of "a"-indexed iterations, n must be a natural number
     and m-n must be a natural number for the ExprTuple to be
     valid (note that iterations may have an extent of zero).
     When an ExprTuple is created, there is not a general check
     that it is valid.  However, when deriving new known truths
     from valid existing known truths, validity is guaranteed to
     be maintained (in particular, specializations that transform
     ExprTuples ensure that validity is maintained).  The joining
     of iterations is valid as long as the original iterations
     are valid, so this process is also one that maintains validity
     which is the thing that is important.
     '''
     from proveit._core_ import KnownTruth
     from .composite import singleOrCompositeExpression
     from .iteration import Iter
     prev_entry = None
     entries = []
     for entry in expressions:
         if isinstance(entry, KnownTruth):
             # Extract the Expression from the KnownTruth:
             entry = entry.expr
         try:
             entry = singleOrCompositeExpression(entry)
             assert isinstance(entry, Expression)
         except:
             raise TypeError("ExprTuple must be created out of "
                             "Expressions.")
         # See if this entry should be joined with the previous
         # entry.
         if isinstance(prev_entry, Iter) and isinstance(entry, Iter):
             from proveit.number import dist_add, dist_subtract, one
             if prev_entry.lambda_map == entry.lambda_map:
                 prev_end_successor = dist_add(prev_entry.end_index, one)
                 next_start_predecessor = dist_subtract(
                     entry.start_index, one)
                 if entry.start_index == prev_end_successor:
                     # Join the entries of the form
                     # (a_i, ..., a_n, a_{n+1}, ..., a_m).
                     prev_entry.end_index = entry.end_index
                     entry = None
                 elif prev_entry.end_index == next_start_predecessor:
                     # Join the entries of the form
                     # (a_i, ..., a_{n-1}, a_{n}, ..., a_m).
                     prev_entry.end_index = entry.end_index
                     entry = None
         if entry is not None:
             # Safe to append the previous entry since it does
             # not join with the new entry.
             if prev_entry is not None: entries.append(prev_entry)
             prev_entry = entry
     if prev_entry is not None:
         # One last entry to append.
         entries.append(prev_entry)
     self.entries = tuple(entries)
     self._lastEntryCoordInfo = None
     self._lastQueriedEntryIndex = 0
     Expression.__init__(self, ['ExprTuple'], self.entries)
示例#6
0
    def substituted(self,
                    exprMap,
                    relabelMap=None,
                    reservedVars=None,
                    assumptions=USE_DEFAULTS,
                    requirements=None):
        '''
        Returns this expression with the substitutions made 
        according to exprMap and/or relabeled according to relabelMap.
        Attempt to automatically expand the iteration if any Indexed 
        sub-expressions substitute their variable for a composite
        (list or tensor).  Indexed should index variables that represent
        composites, but substituting the composite is a signal that
        an outer iteration should be expanded.  An exception is
        raised if this fails.
        '''
        from .composite import _generateCoordOrderAssumptions
        from proveit import ProofFailure, ExprArray
        from proveit.logic import Equals, InSet
        from proveit.number import Less, LessEq, dist_add, \
            zero, one, dist_subtract, Naturals, Integers
        from .composite import _simplifiedCoord
        from proveit._core_.expression.expr import _NoExpandedIteration
        from proveit._core_.expression.label.var import safeDummyVars

        self._checkRelabelMap(relabelMap)
        if relabelMap is None: relabelMap = dict()

        assumptions = defaults.checkedAssumptions(assumptions)
        new_requirements = []
        iter_params = self.lambda_map.parameters
        iter_body = self.lambda_map.body
        ndims = self.ndims
        subbed_start = self.start_indices.substituted(exprMap, relabelMap,
                                                      reservedVars,
                                                      assumptions,
                                                      new_requirements)
        subbed_end = self.end_indices.substituted(exprMap, relabelMap,
                                                  reservedVars, assumptions,
                                                  new_requirements)

        #print("iteration substituted", self, subbed_start, subbed_end)

        # Need to handle the change in scope within the lambda
        # expression.  We won't use 'new_params'.  They aren't relavent
        # after an expansion, this won't be used.
        new_params, inner_expr_map, inner_assumptions, inner_reservations \
            = self.lambda_map._innerScopeSub(exprMap, relabelMap,
                  reservedVars, assumptions, new_requirements)

        # Get sorted substitution parameter start and end
        # values demarcating how the entry array must be split up for
        # each axis.
        all_entry_starts = [None] * ndims
        all_entry_ends = [None] * ndims
        do_expansion = False
        for axis in range(ndims):
            try:
                empty_eq = Equals(dist_add(subbed_end[axis], one),
                                  subbed_start[axis])
                try:
                    # Check if this is an empty iteration which
                    # happens when end+1=start.
                    empty_eq.prove(assumptions, automation=False)
                    all_entry_starts[axis] = all_entry_ends[axis] = []
                    do_expansion = True
                    continue
                except ProofFailure:
                    pass
                param_vals = \
                    iter_body._iterSubParamVals(axis, iter_params[axis],
                                                subbed_start[axis],
                                                subbed_end[axis],
                                                inner_expr_map, relabelMap,
                                                inner_reservations,
                                                inner_assumptions,
                                                new_requirements)
                assert param_vals[0] == subbed_start[axis]
                if param_vals[-1] != subbed_end[axis]:
                    # The last of the param_vals should either be
                    # subbed_end[axis] or known to be
                    # subbed_end[axis]+1.  Let's double-check.
                    eq = Equals(dist_add(subbed_end[axis], one),
                                param_vals[-1])
                    eq.prove(assumptions, automation=False)
                # Populate the entry starts and ends using the
                # param_vals which indicate that start of each contained
                # entry plus the end of this iteration.
                all_entry_starts[axis] = []
                all_entry_ends[axis] = []
                for left, right in zip(param_vals[:-1], param_vals[1:]):
                    all_entry_starts[axis].append(left)
                    try:
                        eq = Equals(dist_add(left, one), right)
                        eq.prove(assumptions, automation=False)
                        new_requirements.append(
                            eq.prove(assumptions, automation=False))
                        # Simple single-entry case: the start and end
                        # are the same.
                        entry_end = left
                    except:
                        # Not the simple case; perform the positive
                        # integrality check.
                        requirement = InSet(dist_subtract(right, left),
                                            Naturals)
                        # Knowing the simplification may help prove the
                        # requirement.
                        _simplifiedCoord(requirement, assumptions, [])
                        try:
                            new_requirements.append(
                                requirement.prove(assumptions))
                        except ProofFailure as e:
                            raise IterationError("Failed to prove requirement "
                                                 "%s:\n%s" % (requirement, e))
                        if right == subbed_end[axis]:
                            # This last entry is the inclusive end
                            # rather than past the end, so it is an
                            # exception.
                            entry_end = right
                        else:
                            # Subtract one from the start of the next
                            # entyr to get the end of this entry.
                            entry_end = dist_subtract(right, one)
                            entry_end = _simplifiedCoord(
                                entry_end, assumptions, requirements)
                    all_entry_ends[axis].append(entry_end)
                # See if we should add the end value as an extra
                # singular entry.  If param_vals[-1] is at the inclusive
                # end, then we have a singular final entry.
                if param_vals[-1] == subbed_end[axis]:
                    end_val = subbed_end[axis]
                    all_entry_starts[axis].append(end_val)
                    all_entry_ends[axis].append(end_val)
                else:
                    # Otherwise, the last param_val will be one after
                    # the inclusive end which we will want to use below
                    # when building the last iteration entry.
                    all_entry_starts[axis].append(param_vals[-1])
                do_expansion = True
            except EmptyIterException:
                # Indexing over a negative or empty range.  The only way this
                # should be allowed is if subbed_end+1=subbed_start.
                Equals(dist_add(subbed_end[axis], one),
                       subbed_start[axis]).prove(assumptions)
                all_entry_starts[axis] = all_entry_ends[axis] = []
                do_expansion = True
            except _NoExpandedIteration:
                pass

        if do_expansion:
            # There are Indexed sub-Expressions whose variable is
            # being replaced with a Composite, so let us
            # expand the iteration for all of the relevant
            # iteration ranges.
            # Sort the argument value ranges.

            # We must have "substition parameter values" along each
            # axis:
            if None in all_entry_starts or None in all_entry_ends:
                raise IterationError("Must expand all axes or none of the "
                                     "axes, when substituting %s" % str(self))

            # Generate the expanded tuple/array as the substition
            # of 'self'.
            shape = [len(all_entry_ends[axis]) for axis in range(ndims)]
            entries = ExprArray.make_empty_entries(shape)
            indices_by_axis = [range(extent) for extent in shape]
            #print('shape', shape, 'indices_by_axis', indices_by_axis, 'sub_param_vals', sub_param_vals)

            extended_inner_assumptions = list(inner_assumptions)
            for axis_starts in all_entry_starts:
                # Generate assumptions that order the
                # successive entry start parameter values
                # must be natural numbers. (This is a requirement for
                # iteration instances and is a simple fact of
                # succession for single entries.)
                extended_inner_assumptions.extend(
                    _generateCoordOrderAssumptions(axis_starts))

            # Maintain lists of parameter values that come before each given entry.
            #prev_param_vals = [[] for axis in range(ndims)]

            # Iterate over each of the new entries, obtaining indices
            # into sub_param_vals for the start parameters of the entry.
            for entry_indices in itertools.product(*indices_by_axis):
                entry_starts = [axis_starts[i] for axis_starts, i in \
                                zip(all_entry_starts, entry_indices)]
                entry_ends = [axis_ends[i] for axis_ends, i in \
                                zip(all_entry_ends, entry_indices)]

                is_singular_entry = True
                for entry_start, entry_end in zip(entry_starts, entry_ends):
                    # Note that empty ranges will be skipped because
                    # equivalent parameter values should be skipped in
                    # the param_vals above.
                    if entry_start != entry_end:
                        # Not a singular entry along this axis, so
                        # it is not a singular entry.  We must do an
                        # iteration for this entry.
                        is_singular_entry = False

                if is_singular_entry:
                    # Single element entry.

                    # Generate the entry by making appropriate
                    # parameter substitutions for the iteration body.
                    entry_inner_expr_map = dict(inner_expr_map)
                    entry_inner_expr_map.update({
                        param: arg
                        for param, arg in zip(iter_params, entry_starts)
                    })
                    for param in iter_params:
                        relabelMap.pop(param, None)
                    entry = iter_body.substituted(entry_inner_expr_map,
                                                  relabelMap,
                                                  inner_reservations,
                                                  extended_inner_assumptions,
                                                  new_requirements)
                else:
                    # Iteration entry.
                    # Shift the iteration parameter so that the
                    # iteration will have the same start-indices
                    # for this sub-range (like shifting a viewing
                    # window, moving the origin to the start of the
                    # sub-range).

                    # Generate "safe" new parameters (the Variables are
                    # not used for anything that might conflict).
                    # Avoid using free variables from these expressions:
                    unsafe_var_exprs = [self]
                    unsafe_var_exprs.extend(exprMap.values())
                    unsafe_var_exprs.extend(relabelMap.values())
                    unsafe_var_exprs.extend(entry_starts)
                    unsafe_var_exprs.extend(entry_ends)
                    new_params = safeDummyVars(ndims, *unsafe_var_exprs)

                    # Make assumptions that places the parameter(s) in the
                    # appropriate range and at an integral coordinate position.
                    # Note, it is possible that this actually represents an
                    # empty range and that these assumptions are contradictory;
                    # but this still suits our purposes regardless.
                    # Also, we will choose to shift the parameter so it
                    # starts at the start index of the iteration.
                    range_expr_map = dict(inner_expr_map)
                    range_assumptions = []
                    shifted_entry_ends = []
                    for axis, (param, new_param, entry_start, entry_end) \
                            in enumerate(zip(iter_params, new_params,
                                             entry_starts, entry_ends)):
                        start_idx = self.start_indices[axis]
                        shift = dist_subtract(entry_start, start_idx)
                        shift = _simplifiedCoord(shift, assumptions,
                                                 new_requirements)
                        if shift != zero:
                            shifted_param = dist_add(new_param, shift)
                        else:
                            shifted_param = new_param
                        range_expr_map[param] = shifted_param
                        shifted_end = dist_subtract(entry_end, shift)
                        shifted_end = _simplifiedCoord(shifted_end,
                                                       assumptions,
                                                       new_requirements)
                        shifted_entry_ends.append(shifted_end)
                        assumption = InSet(new_param, Integers)
                        range_assumptions.append(assumption)
                        assumption = LessEq(entry_start, shifted_param)
                        range_assumptions.append(assumption)
                        # Assume differences with each of the previous
                        # range starts are natural numbers as should be
                        # the case given requirements that have been
                        # met.
                        next_index = entry_indices[axis] + 1
                        prev_starts = all_entry_starts[axis][:next_index]
                        for prev_start in prev_starts:
                            assumption = InSet(
                                dist_subtract(shifted_param, prev_start),
                                Naturals)
                            range_assumptions.append(assumption)
                        next_start = all_entry_starts[axis][next_index]
                        assumption = Less(shifted_param, next_start)
                        range_assumptions.append(assumption)

                    # Perform the substitution.
                    # The fact that our "new parameters" are "safe"
                    # alleviates the need to reserve anything extra.
                    range_lambda_body = iter_body.substituted(
                        range_expr_map, relabelMap, reservedVars,
                        extended_inner_assumptions + range_assumptions,
                        new_requirements)
                    # Any requirements that involve the new parameters
                    # are a direct consequence of the iteration range
                    # and are not external requirements:
                    new_requirements = \
                        [requirement for requirement in new_requirements
                         if requirement.freeVars().isdisjoint(new_params)]
                    entry = Iter(new_params, range_lambda_body,
                                 self.start_indices, shifted_entry_ends)
                # Set this entry in the entries array.
                ExprArray.set_entry(entries, entry_indices, entry)
                '''      
                    # Iteration entry.
                    # Shift the iteration parameter so that the 
                    # iteration will have the same start-indices
                    # for this sub-range (like shifting a viewing 
                    # window, moving the origin to the start of the 
                    # sub-range).

                    # Generate "safe" new parameters (the Variables are
                    # not used for anything that might conflict).
                    # Avoid using free variables from these expressions:
                    unsafe_var_exprs = [self]
                    unsafe_var_exprs.extend(exprMap.values())
                    unsafe_var_exprs.extend(relabelMap.values())
                    unsafe_var_exprs.extend(entry_start_vals)
                    unsafe_var_exprs.extend(entry_end_vals)
                    new_params = safeDummyVars(len(iter_params), 
                                               *unsafe_var_exprs)
                    
                    # Make the appropriate substitution mapping
                    # and add appropriate assumptions for the iteration
                    # parameter(s).
                    range_expr_map = dict(inner_expr_map)
                    range_assumptions = []
                    for start_idx, param, new_param, range_start, range_end \
                            in zip(subbed_start, iter_params, new_params, 
                                   entry_start_vals, entry_end_vals):
                        shifted_param = Add(new_param, subtract(range_start, start_idx))
                        shifted_param = _simplifiedCoord(shifted_param, assumptions,
                                                         requirements)
                        range_expr_map[param] = shifted_param
                        # Include assumptions that the parameters are 
                        # in the proper range.
                        assumption = LessEq(start_idx, new_param)
                        range_assumptions.append(assumption)
                        assumption = InSet(subtract(new_param, start_idx), Naturals)
                        #assumption = LessEq(new_param,
                        #                    subtract(range_end, start_idx))
                        assumption = LessEq(new_param, range_end)
                        range_assumptions.append(assumption)
                    
                    # Perform the substitution.
                    # The fact that our "new parameters" are "safe" 
                    # alleviates the need to reserve anything extra.
                    range_lambda_body = iter_body.substituted(range_expr_map, 
                        relabelMap, reservedVars, 
                        inner_assumptions+range_assumptions, new_requirements)
                    # Any requirements that involve the new parameters 
                    # are a direct consequence of the iteration range 
                    # and are not external requirements:
                    new_requirements = \
                        [requirement for requirement in new_requirements 
                         if requirement.freeVars().isdisjoint(new_params)]
                    range_lambda_map = Lambda(new_params, range_lambda_body)
                    # Obtain the appropriate end indices.
                    end_indices = \
                        [_simplifiedCoord(subtract(range_end, start_idx), 
                                          assumptions, new_requirements) 
                         for start_idx, range_end in zip(subbed_start, 
                                                          entry_end_vals)]
                    entry = Iter(range_lambda_map, subbed_start, end_indices)
                # Set this entry in the entries array.
                ExprArray.set_entry(entries, entry_start_indices, entry)
                '''
            subbed_self = compositeExpression(entries)
        else:
            # No Indexed sub-Expressions whose variable is
            # replaced with a Composite, so let us not expand the
            # iteration.  Just do an ordinary substitution.
            new_requirements = []  # Fresh new requirements.
            subbed_map = self.lambda_map.substituted(exprMap, relabelMap,
                                                     reservedVars, assumptions,
                                                     new_requirements)
            subbed_self = Iter(subbed_map.parameters, subbed_map.body,
                               subbed_start, subbed_end)

        for requirement in new_requirements:
            # Make sure requirements don't use reserved variable in a
            # nested scope.
            requirement._restrictionChecked(reservedVars)
        if requirements is not None:
            requirements += new_requirements  # append new requirements

        return subbed_self