Example #1
0
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
Example #2
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')