def endCorner(self, tensor_entry_loc): ''' Given an absolute tensor entry location, return the absolute location for the "end-corner" of the entry. If the entry is an Iter, then this absolute end corner gives the range of the iteration inclusively. Otherwise, the end-corner is simply tensor_entry_loc; that is, the start and the end are the same for single-element entries. ''' from proveit.number import one, Add, Subtract from .iteration import Iter entry = self[self.relEntryLoc(tensor_entry_loc)] if isinstance(entry, Iter): end_corner = [] for axis, coord in enumerate(tensor_entry_loc): # Add (end-start)+1 of the Iter to get to the end # location of the entry along this axis. orig_end_coord = Add( coord, Subtract(entry.end_indices[axis], entry.start_indices[axis]), one) end_corner.append(self.endCoordSimplifications[orig_end_coord] ) # use the simplified version return end_corner # absolute end-corner for the tensor entry return tensor_entry_loc # single-element entry
def _tensorDictFromIterables(tensor, assumptions, requirements): ''' From nested lists of Expressions, create a tensor dictionary, mapping multi-dimensional indices to Expression elements. Yields location, element pairs that define a tensor. ''' from proveit._core_ import KnownTruth from composite import _simplifiedCoord from proveit.number import zero, one, Add, Subtract try: coord = zero for entry in tensor: # simplify the coordinate before moving on # (the simplified form will be equated with the original in the # sorting relations of the ExprTensor). coord = _simplifiedCoord(coord, assumptions, requirements) if isinstance(entry, KnownTruth): entry = entry.expr # extract the Expression from the KnownTruth if isinstance(entry, Expression): loc = (coord,) if isinstance(entry, Iter) and entry.ndims > 1: loc += (zero,)*(entry.ndims-1) # append zeros for the extra dimensions yield loc, entry # yield the location and element if isinstance(entry, Iter): # skip the coordinate ahead over the Embed expression coord = Add(coord, Subtract(entry.end_indices[0], entry.start_indices[0]), one) else: coord = Add(coord, one) # shift the coordinate ahead by one else: for sub_loc, entry in ExprTensor.TensorDictFromIterables(entry): loc = (coord,)+sub_loc yield loc, entry except TypeError: raise TypeError('An ExprTensor must be a dictionary of indices to elements or a nested iterables of Expressions')
def _makeEntryOrigins(self): ''' entryOrigins maps relative indices that contain tensor elements to the relative indices of the origin for the corresponding entry. Specifically, single-element entries map indices to themselves, but multi-element Iter entries map each of the encompassed relative index location to the origin relative index location where that Iter entry is stored. Raise an ExprTensorError if there are overlapping entries. ''' from .iteration import Iter from proveit.number import Add, Subtract, one # Create the entry_origins dictionary and check for invalid # overlapping entries while we are at it. rel_entry_origins = dict() rel_index_tensor = self.relIndexTensor for rel_entry_loc, entry in rel_index_tensor.items(): if isinstance(entry, Iter): loc = self.tensorLoc(rel_entry_loc) # corner location at the end of the Embed block: end_corner = [] for axis, coord in enumerate(loc): end_coord = Add( coord, Subtract(entry.end_indices[axis], entry.start_indices[axis]), one) end_corner.append(self.endCoordSimplifications[end_coord]) # translate the end corner location to the corresponding relative indices rel_entry_end_corner = self.relEntryLoc(end_corner) # iterate over all of the relative indexed locations from the starting corner to # the ending corner of the Iter block, populating the entry_origins dictionary and # making sure none of the locations overlap with something else. for p in itertools.product(*[ range(start, end) for start, end in zip( rel_entry_loc, rel_entry_end_corner) ]): p = tuple(p) if p in rel_entry_origins: raise ExprTensorError( "Overlapping blocks in the ExprTensor") rel_entry_origins[p] = rel_entry_loc else: # single-element entry. check for overlap and add to the entry_origins dictionary if rel_entry_loc in rel_entry_origins: raise ExprTensorError( "Overlapping blocks in the ExprTensor") rel_entry_origins[rel_entry_loc] = rel_entry_loc # Return the entry_origins dictionary that we generated. return rel_entry_origins
def getElem(self, tensor_loc, assumptions=USE_DEFAULTS, requirements=None): ''' Return the tensor element at the location, given as an Expression, using the given assumptions as needed to interpret the location expression. Required truths, proven under the given assumptions, that were used to make this interpretation will be appended to the given 'requirements' (if provided). ''' from proveit.number import Less, Add, Subtract from iteration import Iter from composite import _simplifiedCoord if len(tensor_loc) != self.ndims: raise ExprTensorError("The 'tensor_loc' has the wrong number of dimensions: %d instead of %d"%(len(tensor_loc), self.ndims)) if requirements is None: requirements = [] # requirements won't be passed back in this case lower_indices = [] upper_indices = [] for coord, sorted_coords in zip(tensor_loc, self.sortedCoordLists): lower, upper = None, None try: lower, upper = Less.insert(sorted_coords, coord, assumptions=assumptions) except: raise ExprTensorError("Could not determine the 'tensor_loc' range within the tensor coordinates under the given assumptions") # The relationship to the lower and upper coordinate bounds are requirements for determining # the element being assessed. requirements.append(Less.sort((sorted_coords[lower], coord), reorder=False, assumptions=assumptions)) requirements.append(Less.sort((coord, sorted_coords[upper]), reorder=False, assumptions=assumptions)) lower_indices.append(lower) upper_indices.append(upper) if tuple(lower_indices) not in self.entryOrigins or tuple(upper_indices) not in self.entryOrigins: raise ExprTensorError("Tensor element could not be found at %s"%str(tensor_loc)) rel_entry_origin = self.relEntryOrigins[lower_indices] if self.relEntryOrigins[upper_indices] != rel_entry_origin: raise ExprTensorError("Tensor element is ambiguous for %s under the given assumptions"%str(tensor_loc)) entry = self[rel_entry_origin] if isinstance(entry, Iter): # indexing into an iteration entry_origin = self.tensorLoc(rel_entry_origin) iter_start_indices = entry.start_indices iter_loc = [Add(iter_start, Subtract(coord, origin)) for iter_start, coord, origin in zip(iter_start_indices, tensor_loc, entry_origin)] simplified_iter_loc = [_simplifiedCoord(coord, assumptions, requirements) for coord in iter_loc] return entry.getInstance(simplified_iter_loc, assumptions=assumptions, requirements=requirements) else: # just a single-element entry assert lower_indices==upper_indices, "A single-element entry should not have been determined if there was an ambiguous range for 'tensor_loc'" return entry
def __init__(self, tensor, shape=None, styles=None, assumptions=USE_DEFAULTS, requirements=tuple()): ''' Create an ExprTensor either with a simple, dense tensor (list of lists ... of lists) or with a dictionary mapping coordinates (as tuples of expressions that represent integers) to expr elements or Blocks. Providing starting and/or ending location(s) can extend the bounds of the tensor beyond the elements that are supplied. ''' from .composite import _simplifiedCoord from proveit._core_ import KnownTruth from proveit.number import Less, Greater, zero, one, num, Add, Subtract assumptions = defaults.checkedAssumptions(assumptions) requirements = [] if not isinstance(tensor, dict): tensor = { loc: element for loc, element in ExprTensor._tensorDictFromIterables( tensor, assumptions, requirements) } # Map direct compositions for the end-coordinate of Iter elements # to their simplified forms. self.endCoordSimplifications = dict() # generate the set of distinct coordinates for each dimension coord_sets = None # simplified versions full_tensor = dict() ndims = None if shape is not None: shape = ExprTensor.locAsExprs(shape) ndims = len(shape) for loc, element in tensor.items(): if isinstance(element, KnownTruth): element = element.expr # extract the Expression from the KnownTruth ndims = len(loc) if coord_sets is None: coord_sets = [set() for _ in range(ndims)] elif len(coord_sets) != ndims: if shape is not None: raise ValueError( "length of 'shape' is inconsistent with number of dimensions for ExprTensor locations" ) else: raise ValueError( "inconsistent number of dimensions for locations of the ExprTensor" ) for axis, coord in enumerate(list(loc)): if isinstance(coord, int): coord = num( coord) # convert from Python int to an Expression loc[axis] = coord coord_sets[axis].add(coord) if isinstance(element, Iter): # Add (end-start)+1 of the Iter to get to the end # location of the entry along this axis. orig_end_coord = Add( coord, Subtract(element.end_indices[axis], element.start_indices[axis]), one) end_coord = _simplifiedCoord(orig_end_coord, assumptions, requirements) self.endCoordSimplifications[orig_end_coord] = end_coord coord_sets[axis].add(end_coord) full_tensor[tuple(loc)] = element if ndims is None: raise ExprTensorError("Empty ExprTensor is not allowed") if ndims <= 1: raise ExprTensorError( "ExprTensor must be 2 or more dimensions (use an ExprList for something 1-dimensional" ) # in each dimension, coord_indices will be a dictionary # that maps each tensor location coordinate to its relative entry index. coord_rel_indices = [] self.sortedCoordLists = [] self.coordDiffRelationLists = [] for axis in range(ndims): # for each axis # KnownTruth sorting relation for the simplified coordinates used along this axis # (something with a form like a < b <= c = d <= e, that sorts the tensor location coordinates): coord_sorting_relation = Less.sort(coord_sets[axis], assumptions=assumptions) sorted_coords = list(coord_sorting_relation.operands) if shape is None: # Since nothing was explicitly specified, the shape is dictacted by extending # one beyond the last coordinate entry. sorted_coords.append(Add(sorted_coords[-1], one)) else: sorted_coords.append( shape[axis] ) # append the coordinate for the explicitly specified shape if sorted_coords[0] != zero: sorted_coords.insert( 0, zero ) # make sure the first of the sorted coordinates is zero. self.sortedCoordLists.append(ExprList(sorted_coords)) # Add in coordinate expressions that explicitly indicate the difference between coordinates. # These may be used in generating the latex form of the ExprTensor. diff_relations = [] for c1, c2 in zip(sorted_coords[:-1], sorted_coords[1:]): diff = _simplifiedCoord(Subtract(c2, c1), assumptions, requirements) # get the relationship between the difference of successive coordinate and zero. diff_relation = Greater.sort([zero, diff], assumptions=assumptions) if isinstance(diff_relation, Greater): if c2 == sorted_coords[-1] and shape is not None: raise ExprTensorError( "Coordinates extend beyond the specified shape in axis %d: %s after %s" % (axis, str(coord_sorting_relation.operands[-1]), str(shape[axis]))) assert tuple(diff_relation.operands) == ( diff, zero), 'Inconsistent Less.sort results' # diff > 0, let's compare it with one now diff_relation = Greater.sort([one, diff], assumptions=assumptions) requirements.append(diff_relation) diff_relations.append(diff_relation) self.coordDiffRelationLists.append(ExprList(diff_relations)) # map each coordinate expression to its index into the sorting_relation operands coord_rel_indices.append( {coord: k for k, coord in enumerate(sorted_coords)}) # convert from the full tensor with arbitrary expression coordinates to coordinates that are # mapped according to sorted relation enumerations. rel_index_tensor = dict() for loc, element in full_tensor.items(): rel_index_loc = ( rel_index_map[coord] for coord, rel_index_map in zip(loc, coord_rel_indices)) rel_index_tensor[rel_index_loc] = element sorted_keys = sorted(rel_index_tensor.keys()) Expression.__init__(self, [ 'ExprTensor', str(ndims), ';'.join(str(key) for key in sorted_keys) ], self.sortedCoordLists + self.coordDiffRelationLists + [rel_index_tensor[key] for key in sorted_keys], styles=styles, requirements=requirements) self.ndims = ndims self.relIndexTensor = rel_index_tensor # entryOrigins maps relative indices that contain tensor elements to # the relative indices of the origin for the corresponding entry. # Specifically, single-element entries map indices to themselves, but # multi-element Iter entries map each of the encompassed # relative index location to the origin relative index location where # that Iter entry is stored. self.relEntryOrigins = self._makeEntryOrigins() # the last coordinates of the sorted coordinates along each eaxis define the shape: self.shape = ExprList( [sorted_coords[-1] for sorted_coords in self.sortedCoordLists])
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 proveit.logic import Equals from proveit.number import Less, LessEq, Subtract, Add, one from composite import _simplifiedCoord from proveit._core_.expression.expr import _NoExpandedIteration assumptions = defaults.checkedAssumptions(assumptions) arg_sorting_assumptions = list(assumptions) new_requirements = [] # Collect the iteration ranges from Indexed sub-Expressions # whose variable is being replaced with a Composite (list or tensor). # If there are not any, we won't expand the iteration at this point. # While we are at it, get all of the end points of the # ranges along each axis (as well as end points +/-1 that may be # needed if there are overlaps): 'special_points'. iter_ranges = set() iter_params = self.lambda_map.parameters special_points = [set() for _ in xrange(len(iter_params))] subbed_start = self.start_indices.substituted(exprMap, relabelMap, reservedVars, assumptions, new_requirements) subbed_end = self.end_indices.substituted(exprMap, relabelMap, reservedVars, assumptions, new_requirements) try: for iter_range in self.lambda_map.body._expandingIterRanges( iter_params, subbed_start, subbed_end, exprMap, relabelMap, reservedVars, assumptions, new_requirements): iter_ranges.add(iter_range) for axis, (start, end) in enumerate(zip(*iter_range)): special_points[axis].add(start) special_points[axis].add(end) # Preemptively include start-1 and end+1 in case it is required for splitting up overlapping ranges # (we won't add simplification requirements until we find we actually need them.) # Not necesary in the 1D case. # Add the coordinate simplification to argument sorting assumtions - # after all, this sorting does not go directly into the requirements. start_minus_one = _simplifiedCoord( Subtract(start, one), assumptions=assumptions, requirements=arg_sorting_assumptions) end_plus_one = _simplifiedCoord( Add(end, one), assumptions=assumptions, requirements=arg_sorting_assumptions) special_points[axis].update( {start_minus_one, end_plus_one}) # Add start-1<start and end<end+1 assumptions to ease argument sorting - # after all, this sorting does not go directly into the requirements. arg_sorting_assumptions.append(Less( start_minus_one, start)) arg_sorting_assumptions.append(Less(end, end_plus_one)) arg_sorting_assumptions.append( Equals(end, Subtract(end_plus_one, one))) # Also add start<=end to ease the argument sorting requirement even though it # may not strictly be true if an empty range is possible. In such a case, we # still want things sorted this way while we don't know if the range is empty or not # and it does not go directly into the requirements. arg_sorting_assumptions.append(LessEq(start, end)) # 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. arg_sorting_relations = [] for axis in xrange(self.ndims): if len(special_points[axis]) == 0: arg_sorting_relation = None else: arg_sorting_relation = Less.sort( special_points[axis], assumptions=arg_sorting_assumptions) arg_sorting_relations.append(arg_sorting_relation) # Put the iteration ranges in terms of indices of the sorting relation operands # (relative indices w.r.t. the sorting relation order). rel_iter_ranges = set() for iter_range in iter_ranges: range_start, range_end = iter_range rel_range_start = tuple([ arg_sorting_relation.operands.index(arg) for arg, arg_sorting_relation in zip( range_start, arg_sorting_relations) ]) rel_range_end = tuple([ arg_sorting_relation.operands.index(arg) for arg, arg_sorting_relation in zip( range_end, arg_sorting_relations) ]) rel_iter_ranges.add((rel_range_start, rel_range_end)) rel_iter_ranges = sorted( self._makeNonoverlappingRangeSet(rel_iter_ranges, arg_sorting_relations, assumptions, new_requirements)) # Generate the expanded list/tensor to replace the iterations. if self.ndims == 1: lst = [] else: tensor = dict() for rel_iter_range in rel_iter_ranges: # get the starting location of this iteration range start_loc = tuple( arg_sorting_relation.operands[idx] for arg_sorting_relation, idx in zip( arg_sorting_relations, rel_iter_range[0])) if rel_iter_range[0] == rel_iter_range[1]: # single element entry (starting and ending location the same) inner_expr_map = dict(exprMap) inner_expr_map.update({ param: arg for param, arg in zip(self.lambda_map.parameters, start_loc) }) for param in self.lambda_map.parameters: relabelMap.pop(param, None) entry = self.lambda_map.body.substituted( inner_expr_map, relabelMap, reservedVars, assumptions, new_requirements) else: # iterate over a sub-range end_loc = tuple( arg_sorting_relation.operands[idx] for arg_sorting_relation, idx in zip( arg_sorting_relations, rel_iter_range[1])) # 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). # Include assumptions that the lambda_map parameters are in the shifted start_loc to end_loc range. range_expr_map = dict(exprMap) range_assumptions = list(assumptions) for start_idx, param, range_start, range_end in zip( self.start_indices, self.lambda_map.parameters, start_loc, end_loc): range_expr_map[param] = Add( param, Subtract(range_start, start_idx)) range_assumptions += Less.sort((start_idx, param), reorder=False, assumptions=assumptions) range_assumptions += Less.sort( (param, Subtract(range_end, start_idx)), reorder=False, assumptions=assumptions) range_lambda_body = self.lambda_map.body.substituted( range_expr_map, relabelMap, reservedVars, range_assumptions, new_requirements) range_lambda_map = Lambda(self.lambda_map.parameters, range_lambda_body) # Add the shifted sub-range iteration to the appropriate starting location. end_indices = [ _simplifiedCoord(Subtract(range_end, start_idx), assumptions, new_requirements) for start_idx, range_end in zip( self.start_indices, end_loc) ] entry = Iter(range_lambda_map, self.start_indices, end_indices) if self.ndims == 1: lst.append(entry) else: tensor[start_loc] = entry if self.ndims == 1: subbed_self = compositeExpression(lst) else: subbed_self = compositeExpression(tensor) except _NoExpandedIteration: # No Indexed sub-Expressions whose variable is # replaced with a Composite, so let us not expand the # iteration. Just do an ordinary substitution. subbed_map = self.lambda_map.substituted(exprMap, relabelMap, reservedVars, assumptions, new_requirements) subbed_self = Iter(subbed_map, subbed_start, subbed_end) for requirement in new_requirements: requirement._restrictionChecked( reservedVars ) # make sure requirements don't use reserved variable in a nested scope if requirements is not None: requirements += new_requirements # append new requirements return subbed_self
def _makeNonoverlappingRangeSet(self, rel_iter_ranges, arg_sorting_relations, assumptions, requirements): ''' Helper method for substituted. Check for overlapping relative iteration ranges, breaking them up when found and returning the new set of ranges with no overlaps. ''' from proveit.number import Add, Subtract, one from composite import _simplifiedCoord owning_range = dict( ) # map relative indices to the owning range; overlap occurs when ownership is contested. nonoverlapping_ranges = set() while len(rel_iter_ranges) > 0: rel_iter_range = rel_iter_ranges.pop() for p in itertools.product( * [xrange(start, end) for start, end in zip(*rel_iter_range)]): p = tuple(p) # Check for contested ownership if p in owning_range and owning_range[ p] in nonoverlapping_ranges: # Split along the first axis that differs, # adding the split ranges back in. If there are still # distinct overlapping ranges after that split, there may be # further splits along different axes. range1, range2 = rel_iter_range, owning_range[p] for axis, (start1, end1, start2, end2) in enumerate(zip(*(range1 + range2))): if start1 != start2 or end1 != end2: # (re)assign range1 to have the earliest start. if start1 > start2: range1, range2 = range2, range1 start1, end1, start2, end2 = start2, end2, start1, end1 if start1 < start2: # add the first range first_range = (tuple(range1[0]), tuple(range1[1])) abs_end = _simplifiedCoord( Subtract( arg_sorting_relations.operands[start2], one), assumptions=assumptions, requirements=requirements) first_range[1][ axis] = arg_sorting_relations.index( abs_end) rel_iter_ranges.add(first_range) mid_end = min(end1, end2) if start2 < min(end1, end2): # add the middle ranges (one from each of the originals # where the overlap occurs. for orig_range in (range1, range2): mid_range = (tuple(orig_range[0]), tuple(orig_range[1])) mid_range[1][axis] = end rel_iter_ranges.add(mid_range) end = max(end1, end2) if mid_end < end: # add the last range last_range = (tuple(range2[0]), tuple(range2[1])) abs_start = _simplifiedCoord( Add( arg_sorting_relations. operands[mid_end], one), assumptions=assumptions, requirements=requirements) first_range[0][ axis] = arg_sorting_relations.index( abs_start) rel_iter_ranges.add(last_range) break # remove/exclude the obsolete originals nonoverlapping_ranges.discard(owning_range[p]) rel_iter_range = None break else: owning_range[p] = rel_iter_range if rel_iter_range is not None: nonoverlapping_ranges.add(rel_iter_range) return nonoverlapping_ranges
def entryRanges(self, base, start_index, end_index, assumptions, requirements): ''' For each entry of the list that is fully or partially contained in the window defined via start_indices and end_indices (as Expressions that can be provably sorted against list indices), yield the start and end of the intersection of the entry range and the window. ''' from proveit.number import one, num, Add, Subtract, Less from proveit.logic import Equals from iteration import Iter from proveit import ProofFailure if requirements is None: requirements = [] # requirements won't be passed back in this case index = num(base) started = False prev_end = None try: start_end_relation = Less.sort([start_index, end_index ]).prove(assumptions=assumptions) if start_end_relation.operands[0] != start_index: # end comes before start: the range is empty. This is the vacuous case. requirements.append(start_end_relation) return yield except: # Unable to prove that the end comes before the start, so assume # this will be a finite iteration (if not, the user can decide # how long to wait before they realize they are missing something). pass # Iterate over the entries and track the true element index, # including ranges of iterations (Iter objects). for i, entry in enumerate(self): if not started: # We have not yet encounted an entry within the desired window, # see if this entry is in the desired window. if index == start_index: started = True # Now we've started else: try: start_relation = Less.sort([start_index, index], reorder=False, assumptions=assumptions) requirements.append(start_relation) if start_relation.operator == Less._operator_ and prev_end is not None: # The start of the window must have occurred before this entry, # and there was a previous entry: yield (start_index, prev_end ) # Do the range for the previous entry. started = True # Now we've started except ProofFailure: pass # We have not started yet. # Obtain the ending index of the entry (entry_end) and the next_index # (entry_end+1). entry_end = index # unless it is an Iter: if isinstance(entry, Iter): entry_span = Subtract(entry.end_index, entry.start_index) entry_end = _simplifiedCoord(Add(index, entry_span), assumptions, requirements) arrived_at_end = False if index == end_index: arrived_at_end = True else: try: index_eq_end = Equals(end_index, index).prove(assumptions=assumptions, automation=False) requirements.append(index_eq_end) arrived_at_end == True except ProofFailure: next_index = _simplifiedCoord(Add(entry_end, one), assumptions, requirements) """ # TO KEEP THINGS SIMPLE, LET'S INSIST THAT THE INDEX MUST MATCH THE END EXACTLY TO STOP # (NOT GOING BEYOND WITHOUT MATCHING). # The exception is when the range is empty which we test at the beginning. # See if this entry takes us to the end of the window or beyond. try: print next_index, end_index Less.sort([next_index, end_index], reorder=False, assumptions=assumptions) except ProofFailure: arrived_at_end = True # we have presumably encountered the end if entry_end != end_index: # we require a proven relation that we are at the end end_relation = Less.sort([end_index, next_index], reorder=False, assumptions=assumptions) requirements.append(end_relation) """ if arrived_at_end: if started: # Yield from the start of the entry to the end of the window: yield (index, end_index) break else: # The full window is within this entry. start_relation = Less.sort([index, start_index], reorder=False, assumptions=assumptions) requirements.append(start_relation) yield ( start_index, end_index ) # Yield the full window that is within a single entry. break elif started: # We have encountered the start but not the end. yield (index, entry_end) # Yield the full range of the entry. index = next_index # Move on to the next entry. prev_end = entry_end if not arrived_at_end: raise IndexError("ExprList index out of range")
def getElem(self, index, base=1, assumptions=USE_DEFAULTS, requirements=None): ''' Return the list element at the index, given as an Expression, using the given assumptions as needed to interpret the location expression. Required truths, proven under the given assumptions, that were used to make this interpretation will be appended to the given 'requirements' (if provided). ''' from proveit.number import num, one, LesserSequence, Less, LessEq, Add, Subtract from proveit.logic import Equals from iteration import Iter from composite import _simplifiedCoord if requirements is None: requirements = [ ] # create the requirements list, but it won't be used sorted_coords = [] less_operators = [] coord = num(base) sorted_coords.append(coord) coord_sort_requirements = [] for entry in self.entries: if isinstance(entry, Iter): coord = _simplifiedCoord( Add(coord, Subtract(entry.end_index, entry.start_index)), assumptions, coord_sort_requirements) less_operators.append( LessEq._operator_ ) # we don't necessarily know if coord < coord+(entry.end_index-entry.start_index) else: coord = _simplifiedCoord(Add(coord, one), assumptions, coord_sort_requirements) less_operators.append( Less._operator_) # we know coord < coord+1 sorted_coords.append(coord) sorted_coords = LesserSequence(less_operators, sorted_coords) try: sorted_coords_with_index = sorted_coords.insert( index, assumptions=assumptions) upper = sorted_coords_with_index.operands.index( index) # coord above 'index' in the sorted order if sorted_coords.operands[upper] == index: lower = upper # 'index' is one of the coordinates elif upper > 0 and sorted_coords_with_index.operators[ upper - 1] == Equals._operator_: lower = upper = upper - 1 # 'index' is '=' to the coordinate below it elif upper < len(sorted_coords_with_index.operators ) and sorted_coords_with_index.operators[ upper] == Equals._operator_: lower = upper # 'index' is '=' to the coordinate after it else: lower = upper - 1 # 'index' is not known to be the same as any of the existing coordinates except: raise ExprListError( "Could not determine the 'index' range within the ExprList coordinates under the given assumptions" ) requirements += coord_sort_requirements[:lower] # The relationship to the lower and upper coordinate bounds are requirements for determining # the element being assessed. if upper > 0 and sorted_coords.operands[lower] != index: requirements.append( LessEq.sort((sorted_coords.operands[lower], index), reorder=False, assumptions=assumptions)) if lower < upper and upper < len( sorted_coords.operands ) and sorted_coords.operands[upper] != index: requirements.append( LessEq.sort((index, sorted_coords.operands[upper]), reorder=False, assumptions=assumptions)) entry = self[lower] if isinstance(entry, Iter): # indexing into an iteration entry_origin = sorted_coords[lower] iter_start_index = entry.start_index iter_loc = Add(iter_start_index, Subtract(index, entry_origin)) simplified_iter_loc = _simplifiedCoord(iter_loc, assumptions, requirements) return entry.getInstance(simplified_iter_loc, assumptions=assumptions, requirements=requirements) else: # just a single-element entry assert lower == upper, "A single-element entry should not have been determined if there was an ambiguous range for the 'index'" return entry
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. If the indexed variable has been replaced with a Composite (ExprList or ExprTensor), this should return the indexed element. Only a Variable should be indexed via a Indexed expression; once the Variable is replaced with a Composite, the indexing should be actualized. ''' from composite import Composite, _simplifiedCoord from proveit.number import num, Subtract, isLiteralInt from expr_list import ExprList from expr_tensor import ExprTensor new_requirements = [] subbed_var = self.var.substituted( exprMap, relabelMap, reservedVars) # requirements not needed for variable substitution subbed_indices = self.indices.substituted( exprMap, relabelMap, reservedVars, assumptions=assumptions, requirements=new_requirements) result = None if isinstance(subbed_var, Composite): # The indexed expression is a composite. # Now see if the indices can be simplified to integers; if so, # we can attempt to extract the specific element. # If there is an IndexingError (i.e., we cannot get the element # because the Composite has an unexpanding Iter), # default to returning the subbed Indexed. indices = subbed_indices if isinstance(subbed_var, ExprTensor) and self.base != 0: # subtract off the base if it is not zero indices = [ Subtract(index, num(self.base)) for index in indices ] indices = [ _simplifiedCoord(index, assumptions, new_requirements) for index in indices ] if isinstance(subbed_var, ExprList): if isLiteralInt(indices[0]): result = subbed_var[indices[0].asInt() - self.base] else: raise IndexedError( "Indices must evaluate to literal integers when substituting an Indexed expression, got " + str(indices[0])) elif isinstance(subbed_var, ExprTensor): result = subbed_var.getElem(indices, assumptions=assumptions, requirements=new_requirements) for requirement in new_requirements: requirement._restrictionChecked( reservedVars ) # make sure requirements don't use reserved variable in a nested scope if requirements is not None: requirements += new_requirements # append new requirements # If the subbed_var has not been replaced with a Composite, # just return the Indexed operation with the substitutions made. if result is not None: return result else: return Indexed(subbed_var, subbed_indices, base=self.base)
def getElem(self, index, base=1, assumptions=USE_DEFAULTS, requirements=None): ''' Return the list element at the index, given as an Expression, using the given assumptions as needed to interpret the location expression. Required truths, proven under the given assumptions, that were used to make this interpretation will be appended to the given 'requirements' (if provided). ''' from proveit.number import num, one, lesserSequence, Less, LessEq, Add, Subtract from proveit.logic import Equals from proveit.relation import TransitivityException from .iteration import Iter from .composite import _simplifiedCoord if len(self) == 0: raise ValueError("An empty ExprList has no elements to get") if requirements is None: requirements = [ ] # create the requirements list, but it won't be used coord = num(base) try: for entry in self.entries: if isinstance(entry, Iter): # An Iter entry. First, check whether it is an empty iteration. entry_start_end_relation = Less.sort( [entry.start_index, entry.end_index], assumptions=assumptions) if not entry_start_end_relation.operator == Equals._operator_: # start and end are not determined to be equal (if they were, the # iteration would represent a single element). if entry_start_end_relation.operands[ 0] == entry.end_index: if entry_start_end_relation.operator == LessEq._operator_: # We don't know if the iteration is empty. raise ExprListError( "Could not determine if an Iter entry of the ExprList is empty, so we could not determine the 'index' element." ) # empty iteration. skip it, but knowing it is empty is an important requirement requirements.append( entry_start_end_relation ) # need to know: end-of-entry < start-of-entry continue # shift 'coord' to the end of the entry next_coord = _simplifiedCoord( Add(coord, Subtract(entry.end_index, entry.start_index)), assumptions, requirements) # check whether or not the 'index' is within this entry. index_entryend_relation = Less.sort( [index, next_coord], assumptions=assumptions) if index_entryend_relation.operands[0] == index: # 'index' within this particular entry iter_start_index = entry.start_index entry_origin = coord if index == entry_origin: # special case - index at the entry origin return entry.getInstance(iter_start_index, assumptions=assumptions, requirements=requirements) iter_loc = Add(iter_start_index, Subtract(index, entry_origin)) simplified_iter_loc = _simplifiedCoord( iter_loc, assumptions, requirements) return entry.getInstance(simplified_iter_loc, assumptions=assumptions, requirements=requirements) coord = next_coord index_coord_relation = Less.sort([index, coord], assumptions=assumptions) if index_coord_relation.operator == Equals._operator_: # 'index' at this particular single-element entry if index_coord_relation.lhs != index_coord_relation.rhs: requirements.append( index_coord_relation ) # need to know: index == coord, if it's non-trivial return entry elif index_coord_relation.operands[0] == index: # 'index' is less than the 'coord' but not known to be equal to the 'coord' but also # not determined to be within a previous entry. So we simply don't know enough. raise ExprListError( "Could not determine the 'index'-ed element of the ExprList" ) coord = _simplifiedCoord(Add(coord, one), assumptions, requirements) except TransitivityException: raise ExprListError( "Could not determine the 'index'-ed element of the ExprList.") raise IndexError("Index, %s, past the range of the ExprList, %s" % (str(index), str(self)))