def X(pred): """Returns a function that transforms predicates by casting accesses to 't1' to 'x1' and accesses to 't2' to 'x2'. Example: Here is an example of casting an example predicate:: # This predicate tests whether a bound's 't2' value is greater # than its 't1' value def example_pred(bounds): return bounds['t2'] > bounds['t1'] # t1 = 0, t2 = 1, x1 = 1, x2 = 0, y1 = 1, y2 = 0 higher_t2_lower_x2 = Bounds3D(0, 1, 1, 0, 1, 0) example_pred(higher_t2_lower_x2) # this is True, since t2 > t1 Bounds3D.X(example_pred)(higher_t2_lower_x2) # this is False, since x2 < x1 Arg: pred: The predicate to cast. Returns: The same predicate as ``pred``, except accesses to 't1' are cast to 'x1', and accesses to 't2' are cast to 'x2'. """ return Bounds.cast({'t1': 'x1', 't2': 'x2'})(pred)
def T(pred): """Returns a function that transforms predicates by casting accesses to 't1' to 't1' and accesses to 't2' to 't2'. This doesn't actually transform anything, but it's a nice helper function for readability. Arg: pred: The predicate to cast. Returns: The same predicate as ``pred``. """ return Bounds.cast({'t1': 't1', 't2': 't2'})(pred)
def map_output(intrvl, overlapped): # Take only nontrivial overlaps to_subtract = sorted([ i for i in overlapped if (i.size(axis) > 0 and Bounds.cast({ 't1': axis[0], 't2': axis[1] })(overlaps())(intrvl, i)) ], key=lambda i: (i[axis[0]], i[axis[1]])) if len(to_subtract) == 0: return [intrvl.copy()] else: return compute_difference(intrvl, to_subtract)
def coalesce(self, axis, bounds_merge_op, payload_merge_op=lambda p1, p2: p1, predicate=None, epsilon=0): """Recursively merge all intervals that are touching or overlapping along ``axis``. Merge intervals in self if they meet, overlap, or are up to ``epsilon`` apart along ``axis``. If a predicate is specified, intervals will be merged if they meet/overlap and satisfy the predicate. Repeat the process until all such intervals are merged. Merges the bounds with ``bounds_merge_op`` and merges payloads with ``payload_merge_op``. Args: axis: The axis to coalesce on. bounds_merge_op: A function that takes two bounds and returns a merged version of both of them. Along ``axis``, this function should return a bound that spans the two bounds. payload_merge_op (optional): A function that takes in two payloads and merges them. Defaults to a function that returns the first of the two payloads. predicate (optional): A function that takes an interval that is currently being coalesced and a new interval and returns whether or not the two intervals should be merged. epsilon (optional): The slack for judging if Intervals meet or overlap. Must be nonnegative. Defaults to 0 (no slack). Returns: A new IntervalSet of intervals that are disjoint along ``axis`` and are at least ``epsilon`` apart. """ if (len(self._intrvls) == 0): return self new_coalesced_intrvls = [] #tracks all intervals that are currently experiencing merging current_intrvls = [] sorted_intervals = self._intrvls.copy() sorted_intervals = sorted(sorted_intervals, key=lambda intrvl: (intrvl[axis[0]], intrvl[axis[1]])) for intrvl in sorted_intervals: new_current_intrvls = [] for cur in current_intrvls: if Bounds.cast({ axis[0] : 't1', axis[1] : 't2' })(or_pred(overlaps(), before(max_dist=epsilon)))(cur, intrvl): #adds overlapping intervals to new_current_intrvls new_current_intrvls.append(cur) else: #adds all non-overlapping intervals to new_coalesced_intrvls new_coalesced_intrvls.append(cur) current_intrvls = new_current_intrvls matched_intrvl = None loc = len(current_intrvls) - 1 #if current_intrvls is empty, we need to start constructing a new set of coalesced intervals if len(current_intrvls) == 0: current_intrvls.append(intrvl.copy()) continue if predicate is None: matched_intrvl = current_intrvls[-1] else: for index, cur in enumerate(current_intrvls): if predicate(cur, intrvl): matched_intrvl = cur loc = index #if no matching interval is found, this implies that intrvl should be the start of a new coalescing interval if matched_intrvl is None: current_intrvls.append(intrvl) else: current_intrvls[loc] = Interval( bounds_merge_op(matched_intrvl['bounds'], intrvl['bounds']), payload_merge_op(matched_intrvl['payload'], intrvl['payload']) ) for cur in current_intrvls: new_coalesced_intrvls.append(cur) return IntervalSet(new_coalesced_intrvls)
def coalesce(self, axis, bounds_merge_op, payload_merge_op=lambda p1, p2: p1, predicate=None, epsilon=0): """Recursively merge all intervals that are touching or overlapping along ``axis``. Merge intervals in self if they meet, overlap, or are up to ``epsilon`` apart along ``axis``. Repeat the process until all such intervals are merged. Merges the bounds with ``bounds_merge_op`` and merges payloads with ``payload_merge_op``. Args: axis: The axis to coalesce on. bounds_merge_op: A function that takes two bounds and returns a merged version of both of them. Along ``axis``, this function should return a bound that spans the two bounds. payload_merge_op (optional): A function that takes in two payloads and merges them. Defaults to a function that returns the first of the two payloads. epsilon (optional): The slack for judging if Intervals meet or overlap. Must be nonnegative. Defaults to 0 (no slack). Returns: A new IntervalSet of intervals that are disjoint along ``axis`` and are at least ``epsilon`` apart. """ if (len(self._intrvls) == 0): return self new_coalesced_intrvls = [] current_intrvls = [] for intrvl in self._intrvls: for cur in current_intrvls: #adds any intervals that occured before the start of intrvl if not Bounds.cast({ axis[0]: 't1', axis[1]: 't2' })(or_pred(overlaps(), before(max_dist=epsilon)))(cur, intrvl): new_coalesced_intrvls.append(cur) #re-update contents of current_intrvls ==> contains all intervals that overlap current_intrvls = [ cur for cur in current_intrvls if Bounds.cast({ axis[0]: 't1', axis[1]: 't2' })(or_pred(overlaps(), before(max_dist=epsilon)))(cur, intrvl) ] matched_intrvl = None loc = len(current_intrvls) - 1 if len(current_intrvls) == 0: current_intrvls.append(intrvl.copy()) continue if predicate is None: matched_intrvl = current_intrvls[-1] else: for index, cur in enumerate(current_intrvls): if predicate(cur, intrvl): matched_intrvl = cur loc = index if matched_intrvl is None: current_intrvls.append(intrvl) else: current_intrvls[loc] = Interval( bounds_merge_op(matched_intrvl['bounds'], intrvl['bounds']), payload_merge_op(matched_intrvl['payload'], intrvl['payload'])) for cur in current_intrvls: new_coalesced_intrvls.append(cur) return IntervalSet(new_coalesced_intrvls)
def execute(self): while True: if len(self.publishable_coalesced_intrvls) > 0 and \ (len(self.pending_coalesced_intrvls) == 0 or \ self.publishable_coalesced_intrvls[0] < self.pending_coalesced_intrvls[0]): # the above condition ensures output t1 increases monotoically. self.publish(self.publishable_coalesced_intrvls.pop(0)) return True elif self.done: return False intrvl = self.instream.get() # logger.debug(f"got 1 input: {intrvl}") if intrvl is None: self.publishable_coalesced_intrvls = sorted(self.publishable_coalesced_intrvls + self.pending_coalesced_intrvls) self.pending_coalesced_intrvls.clear() self.done = True continue # logger.debug(f"publishable: {self.publishable_coalesced_intrvls}") # logger.debug(f"pending: {self.pending_coalesced_intrvls}") bounds_merge_op = self.bounds_merge_op payload_merge_op = self.payload_merge_op interval_merge_op = self.interval_merge_op predicate = self.predicate epsilon = self.epsilon axis = self.axis distance = self.distance new_coalesced_intrvls = self.publishable_coalesced_intrvls current_intrvls = self.pending_coalesced_intrvls new_current_intrvls = [] for cur in current_intrvls: if Bounds.cast({ axis[0] : 't1', axis[1] : 't2' })(or_pred(overlaps(), before(max_dist=epsilon)))(cur, intrvl): # add overlapping/near intervals to new_current_intrvls new_current_intrvls.append(cur) else: # others intervals can be released, add to new_coalesced_intrvls # logger.debug(f"Adding to publishable: {cur}") new_coalesced_intrvls.append(cur) current_intrvls = new_current_intrvls #if current_intrvls is empty, we need to start constructing a new set of pending intervals if len(current_intrvls) == 0: current_intrvls.append(intrvl) self.publishable_coalesced_intrvls = sorted(new_coalesced_intrvls) self.pending_coalesced_intrvls = sorted(current_intrvls) continue matched_intrvl = None min_dist = None loc = None for index, cur in enumerate(current_intrvls): if predicate(cur, intrvl): d = distance(cur, intrvl) if min_dist is None or d < min_dist: # update winner matched_intrvl = cur loc = index min_dist = d # if no matching interval is found, this implies that intrvl should be the start of a new coalescing interval if matched_intrvl is None: current_intrvls.append(intrvl) else: # finally, do the merge if interval_merge_op is not None: current_intrvls[loc] = interval_merge_op(matched_intrvl, intrvl) else: current_intrvls[loc] = Interval( bounds_merge_op(matched_intrvl['bounds'], intrvl['bounds']), payload_merge_op(matched_intrvl['payload'], intrvl['payload']) ) self.publishable_coalesced_intrvls = sorted(new_coalesced_intrvls) self.pending_coalesced_intrvls = sorted(current_intrvls)