def objective(points, centers): """Calculates the distance between points and centers. :param points: list of points :param centers: list of points :return: float""" if centers and points: return max([min([geometry.distance(p, c) for c in centers]) for p in points]) else: return 0
def brandenberg_roth(k, points, epsilon=1): """This function calculates a geometric k-center by applying a branch-and-bound algorithm by René Brnadenberg and Lucia Roth. See "New Algorithms for k-Center and Extensions" for details. A 2-dimensional space and unit balls as containers are assumed. :param k: int :param points: list of points :param epsilon: float :return: list of points """ global __recursion_depth global __recursions global __warnings global __skip_count __recursion_depth, __recursions, __warnings, __skip_count = 0, 0, 0, 0 State = collections.namedtuple('State', ('core', 'remainings', 'rho', 'centers', 'recursion_depth', 'uid', 'error')) Result = collections.namedtuple('Result', ('core', 'remainings', 'rho', 'centers', 'lower_bound', 'upper_bound', 'state')) upper_bound = float('inf') # estimate = geometry.kcenter.gonzalez(k, points) # rho_bound = objective(points, estimate) stack = [ State( [[] for i in range(k)], # core points, # remainings [0 for i in range(k)], # rho [(0,0) for i in range(k)], # centers 0, # recursion_depth __recursions, # __id 0, # error ) ] result = None while stack: state = stack.pop() lower_bound = max(state.rho) if lower_bound > (upper_bound * (1 + epsilon)) * (1 + state.error): __skip_count += 1 # Are any points left? if not state.remainings: __recursion_depth = max(__recursion_depth, state.recursion_depth) # As no more points are left, the lower bound is an upper bound upper_bound = min(upper_bound, lower_bound) # current_objective < result.upper_bound if not result or lower_bound < result.upper_bound: result = Result(state.core, state.remainings, state.rho, state.centers, lower_bound, lower_bound, state.uid) continue # Compute delta and keep some results for later use p = max(state.remainings, key=lambda p: objective([p], state.centers)) delta = {i: geometry.distance(p, state.centers[i]) for i in range(k)} delta_min = min(delta.values()) # As k is usually small, this could be over-optimization # Update the global upper bound current_objective = max(delta_min, lower_bound) #current_objective = objective(points, state.centers) upper_bound = min(upper_bound, current_objective) if (1 + epsilon) * lower_bound > upper_bound:#upper_bound: #delta_min: __recursion_depth = max(__recursion_depth, state.recursion_depth) if not result or current_objective < result.upper_bound: # lower_bound < result.lower_bound: result = Result(state.core, state.remainings, state.rho, state.centers, lower_bound, current_objective, state.uid) continue else: # Sort clusters descending by distance to p empty_set = False for i in sorted(delta, key=delta.get, reverse=True): # Skip unnessesary permutations if not state.core[i]: if empty_set: continue else: empty_set = True # Recompute c, rho, core # deepcopy calls are expensive! core = [c[:] for c in state.core] core[i].append(p) remainings = state.remainings[:] remainings.remove(p) rho = state.rho[:] centers = state.centers[:] mb = miniball.Miniball(core[i]) if not mb.is_valid(): logger.debug('Invalid miniball detected') __warnings += 1 rho[i] = math.sqrt(mb.squared_radius()) centers[i] = mb.center() if max(rho) <= (upper_bound * (1 + epsilon)) * (1 + mb.relative_error()): logger.debug('[{recursion_depth}] Add {p} to core[{0}]: {1}'.format( i, rho[i], p=p, recursion_depth=state.recursion_depth)) __recursions += 1 new_state = State(core, remainings, rho, centers, state.recursion_depth + 1, __recursions, mb.relative_error()) stack.append(new_state) if result: logger.info('Objective bounded by {0} and {1}'.format(result.lower_bound, upper_bound)) return result.centers else: raise ValueError('No result found')