def get_node_by_monotonic_function(self, function, value, rounding="closest", end_node=None): ''' Get a node by specifying a measure function and a desired value. The function must be a monotonic rising function on the timeline. See documentation of garlicsim.general_misc.binary_search.binary_search for details about rounding options. ''' assert rounding in ["high", "low", "exact", "both", "closest"] if end_node is None: correct_both_for_end_node = lambda both: both else: def correct_both_for_end_node(both): new_both = list(both) end_clock = end_node.state.clock if new_both[0] and new_both[0].state.clock >= end_clock: new_both[0] = end_node if new_both[1] and new_both[1].state.clock >= end_clock: new_both[1] = None return tuple(new_both) low = self.root if function(low) >= value: both = correct_both_for_end_node((None, low)) return binary_search.make_both_data_into_preferred_rounding \ (both, function, value, rounding) ''' Now we've established that the first node in the path has a lower value than what we're looking for. ''' for thing in self.iterate_blockwise(): if isinstance(thing, Block): first = thing[0] if function(first) >= value: both = correct_both_for_end_node((low, first)) return binary_search.make_both_data_into_preferred_rounding \ (both, function, value, rounding) last = thing[-1] if function(last) >= value: # It's in the block both = binary_search.binary_search(thing, function, value, rounding="both") both = correct_both_for_end_node(both) return binary_search.make_both_data_into_preferred_rounding \ (both, function, value, rounding) else: low = last continue else: # thing is a Node if function(thing) >= value: both = correct_both_for_end_node((low, thing)) return binary_search.make_both_data_into_preferred_rounding \ (both, function, value, rounding) else: low = thing continue ''' If the flow reached here, that means that even the last node in the path has lower value than the value we're looking for. ''' both = correct_both_for_end_node((low, None)) return binary_search.make_both_data_into_preferred_rounding \ (both, function, value, rounding)
def __get_node_by_monotonic_function_with_both_rounding(self, function, value): ''' Get a node by specifying a measure function and a desired value. The function must be a monotonic rising function on the timeline. The rounding option used is `binary_search.BOTH`. Note that this function does not let you specify an end node. Currently we're not optimizing for the case where you have an end node and this function might waste resources exploring beyond it. ''' root = self.root cmp_root = cmp(function(root), value) if cmp_root == 1: # function(root) > value return (None, root) if cmp_root == 0: # function(root) == value return (root, root) assert cmp_root == -1 # and function(root) < value # Now we've established that the first node in the path has a strictly # lower value than what we're looking for. # A rule we will strictly obey in this function: `low` will always be a # member whose value is lower than the desired value. (Strictly lower, # meaning not lower-or-equal.) low = self.root for thing in self.iterate_blockwise(): # Rule: Every time we inspect a new node/block, `low` will be the # node that is its immediate parent. i.e. The highest node possible # from those that we have previously examined. if isinstance(thing, Block): block = thing first = block[0] cmp_first = cmp(function(first), value) if cmp_first == -1: # function(first) < value low = first elif cmp_first == 0: # function(first) == value return (first, first) else: # cmp_first == 1 and function(first) > value return (low, first) # At this point we know that the first node in the block has a # strictly lower value than the target value. last = block[-1] cmp_last = cmp(function(last), value) if cmp_last == -1: # function(last) < value low = last continue elif cmp_last == 0: # function(last) == value return (last, last) else: # cmp_last == 1 and function(last) > value # The two final results are both in the block. return binary_search.binary_search( block, function, value, rounding=binary_search.BOTH ) else: # thing is a Node node = thing cmp_node = cmp(function(node), value) if cmp_node == -1: # function(node) < value low = node continue elif cmp_node == 0: # function(node) == value return (node, node) else: # function(node) > value return (low, node) # If the flow reached here, that means that even the last node in the # path has lower value than the value we're looking for. return (low, None)
def __get_node_by_monotonic_function_with_both_rounding( self, function, value): ''' Get a node by specifying a measure function and a desired value. The function must be a monotonic rising function on the timeline. The rounding option used is `binary_search.BOTH`. Note that this function does not let you specify a tail node. Currently we're not optimizing for the case where you have a tail node and this function might waste resources exploring beyond it. ''' root = self.root cmp_root = cmp(function(root), value) if cmp_root == 1: # function(root) > value return (None, root) if cmp_root == 0: # function(root) == value return (root, root) assert cmp_root == -1 # and function(root) < value # Now we've established that the first node in the path has a strictly # lower value than what we're looking for. # A rule we will strictly obey in this function: `low` will always be a # member whose value is lower than the desired value. (Strictly lower, # meaning not lower-or-equal.) low = self.root for thing in self.iterate_blockwise(): # Rule: Every time we inspect a new node/block, `low` will be the # node that is its immediate parent. i.e. The highest node possible # from those that we have previously examined. if isinstance(thing, Block): block = thing first = block[0] cmp_first = cmp(function(first), value) if cmp_first == -1: # function(first) < value low = first elif cmp_first == 0: # function(first) == value return (first, first) else: # cmp_first == 1 and function(first) > value return (low, first) # At this point we know that the first node in the block has a # strictly lower value than the target value. last = block[-1] cmp_last = cmp(function(last), value) if cmp_last == -1: # function(last) < value low = last continue elif cmp_last == 0: # function(last) == value return (last, last) else: # cmp_last == 1 and function(last) > value # The two final results are both in the block. return binary_search.binary_search( block, function, value, rounding=binary_search.BOTH) else: # thing is a Node node = thing cmp_node = cmp(function(node), value) if cmp_node == -1: # function(node) < value low = node continue elif cmp_node == 0: # function(node) == value return (node, node) else: # function(node) > value return (low, node) # If the flow reached here, that means that even the last node in the # path has lower value than the value we're looking for. return (low, None)