def search_true_instance(f, a, b, precision_digits=3, maxiter=10, log=None): """Find x in [a, b] where f is True, limiting search to values with precision_digits significant figures. Returns x, low_bound, high_bound where low_bound and high_bound are either the search bounds a or b, or closer values to x where f was still found to be False. # TODO: If asked for precision_digits=5, first search with precision_digits=1, then 2, etc. print(search_true_instance(lambda x: 11 < x < 13, 0, 40)) print(search_true_instance(lambda x: x < 13, 0, 1000)) """ log = logging.getLogger('search_true_instance') values_searched = [a, b] log.debug("Starting exploratory search in [%s, %s]" % (a, b)) for iter_i in range(maxiter): # First test halfway, point then 1/4 and 3/4, then 1/8, 3/8, 5/8, 7/8, etc. fractions = 2**(iter_i + 1) search_points = [round_to_digits(a + (b - a)*fr, precision_digits) for fr in np.arange(1, fractions, 2)/fractions] log.debug("Searching %s - %s (%d points)" % (search_points[0], search_points[-1], len(search_points))) for x_i, x in enumerate(search_points): if f(x): values_searched = np.array(values_searched) return x, np.max(values_searched[values_searched < x]), np.min(values_searched[values_searched > x]) else: values_searched.append(x) if len(search_points) > 1 and np.any(np.diff(search_points) == 0): raise SearchFailedException("No true value found in search region [%s, %s], " "but search depth now lower than precision digits (%s). " "Iteration %d." % (a, b, precision_digits, iter_i)) raise ValueError("Exploratory search failed to converge or terminate - bug? excessive precision?")
def get_values_and_likelihoods(self, mu, precision_digits=None): if mu == 0: # Return two possible values: 0 and 1, with probability 1 and 0 # There must be more than one possible value to avoid problems # e.g. upper limit should return infinite interval, lower limit single point at 0 # for this we need to check if 1 is included or not return np.array([0, 1]), np.array([1, 0]) # Step size step_size = max(1, 10 ** (int(np.log10(mu)) - precision_digits)) max_mu = round_to_digits(4 + mu + 4 * np.sqrt(mu), precision_digits) values = np.arange(0, max_mu, step_size) return values, stats.poisson.pmf(values, mu=mu)
def get_values_and_likelihoods(self, mu, precision_digits=None): if mu == 0: # Return two possible values: 0 and 1, with probability 1 and 0 # There must be more than one possible value to avoid problems # e.g. upper limit should return infinite interval, lower limit single point at 0 # for this we need to check if 1 is included or not return np.array([0, 1]), np.array([1, 0]) # Step size step_size = max(1, 10 **(int(np.log10(mu)) - precision_digits)) max_mu = round_to_digits(4 + mu + 4 * np.sqrt(mu), precision_digits) values = np.arange(0, max_mu, step_size) return values, stats.poisson.pmf(values, mu=mu)
def __call__(self, observation, precision_digits=None, search_region=None): """Perform Neynman construction to get confidence interval on event rate for observation. """ if precision_digits is None: precision_digits = self.precision_digits if search_region is None: search_region = [0, round_to_digits(10 + 3 * len(observation), precision_digits)] if self.statistic.mu_dependent: value = self.statistic(observation, self.statistic.mus) else: value = self.statistic(observation, None) self.log.debug("Statistic evaluates to %s" % value) return self.get_confidence_interval(value, precision_digits=precision_digits, search_region=search_region)
def __call__(self, observation, hypothesis=None): n_sparsest = self.observation_to_n_sparsest(observation) child_limits = np.zeros(self.n_bins) precision_digits = self.child_statistics[0].precision_digits child_limit_search_region = [0, round_to_digits(10 + 6 * len(observation), precision_digits)] for interval_size in range(self.n_bins): # print("Getting child limit for seeing %d events in interval size %d" % (n_sparsest[interval_size], # interval_size)) child_limits[interval_size] = self.child_statistics[interval_size].get_confidence_interval( n_sparsest[interval_size], precision_digits=precision_digits, search_region=child_limit_search_region)[1] # print("Result is %s" % child_limits[interval_size]) self.last_limit_was_set_on_thisplusone_bins = np.argmin(child_limits) return child_limits[self.last_limit_was_set_on_thisplusone_bins]
def bisect_search(f, a, b, precision_digits=2, maxiter=1e2): """Find x in [a, b] where f changes from True to False by bisection, limiting search to values with precision_digits significant figures. This is useful if f can cache its results: otherwise just use e.g. scipy.optimize.brentq with rtol. Avoid scipy.optimize.bisect with rtol, results seem seem to depend heavily on initial bounds: bug?? # TODO: If asked for precision_digits=5, first search with precision_digits=1, then 2, etc. """ log = logging.getLogger('bisect_search') # Which of the bounds gives True? Can't be both! if f(a) == f(b): raise ValueError("f must not be true or false on both bounds") true_on_a = f(a) log.debug("Starting search between %s (%s) and %s (%s)" " with %d precision digits" % (a, f(a), b, f(b), precision_digits)) # Do a bisection search, sticking to precision_digits for iter_i in range(int(maxiter)): # Find the new bisection point x = (a + b) / 2 x = round_to_digits(x, precision_digits) # If we are down to a single point, return that if x == a or x == b: return x true_on_x = f(x) # Update the appropriate bound if true_on_a: if true_on_x: a = x else: b = x else: if true_on_x: b = x else: a = x log.debug("Iteration %d, searching between [%s and %s], last x was %s (%s)" % (iter_i, a, b, x, true_on_x)) else: raise RuntimeError("Infinite loop encountered in bisection search!")
def __call__(self, observation, hypothesis=None): n_sparsest = self.observation_to_n_sparsest(observation) child_limits = np.zeros(self.n_bins) precision_digits = self.child_statistics[0].precision_digits child_limit_search_region = [ 0, round_to_digits(10 + 6 * len(observation), precision_digits) ] for interval_size in range(self.n_bins): # print("Getting child limit for seeing %d events in interval size %d" % (n_sparsest[interval_size], # interval_size)) child_limits[interval_size] = self.child_statistics[ interval_size].get_confidence_interval( n_sparsest[interval_size], precision_digits=precision_digits, search_region=child_limit_search_region)[1] # print("Result is %s" % child_limits[interval_size]) self.last_limit_was_set_on_thisplusone_bins = np.argmin(child_limits) return child_limits[self.last_limit_was_set_on_thisplusone_bins]