예제 #1
0
def test_priority_queue():
    """
    Ensure the priority set is sorting elements correctly.
    """
    random_elements = [i for i in range(10)]
    shuffle(random_elements)

    pq = PriorityQueue()
    for e in random_elements:
        pq.push(e)

    random_elements.sort()

    assert [e for e in pq] == random_elements
    assert pq.peek() == random_elements[0]

    output = []
    while len(pq) > 0:
        output.append(pq.pop())
    assert output == random_elements

    for e in random_elements:
        pq.push(e)
    assert len(pq) == 10

    pq.update_cost_limit(5)
    assert len(pq) == 6

    output = []
    while len(pq) > 0:
        output.append(pq.pop())
    assert output == random_elements[:6]

    pq = PriorityQueue(node_value=lambda x: x, max_length=3)
    pq.push(6)
    pq.push(0)
    pq.push(2)
    pq.push(6)
    pq.push(7)

    assert len(pq) == 3
    assert list(pq) == [0, 2, 6]

    pq.update_cost_limit(5)

    assert len(pq) == 2
    assert pq.peek() == 0
    assert pq.peek_value() == 0
    assert pq.pop() == 0
    assert pq.peek() == 2
    assert pq.peek_value() == 2
    assert pq.pop() == 2
    assert len(pq) == 0
예제 #2
0
def local_beam_search(problem, beam_width=1, max_sideways=0,
                      graph_search=True, cost_limit=float('-inf')):
    """
    A variant of :func:`py_search.informed_search.beam_search` that can be
    applied to local search problems.  When the beam width of 1 this approach
    yields behavior similar to :func:`hill_climbing`.

    :param problem: The problem to solve.
    :type problem: :class:`py_search.base.Problem`
    :param beam_width: The size of the search beam.
    :type beam_width: int
    :param max_sideways: Specifies the max number of contiguous sideways moves. 
    :type max_sideways: int
    :param graph_search: Whether to use graph search (no duplicates) or tree
        search (duplicates)
    :type graph_search: Boolean
    """
    b = None
    bv = float('inf')
    sideways_moves = 0

    fringe = PriorityQueue(node_value=problem.node_value)
    fringe.push(problem.initial)

    while len(fringe) < beam_width:
        fringe.push(problem.random_node())
    
    if graph_search:
        closed = set()
        closed.add(problem.initial)

    while len(fringe) > 0 and sideways_moves <= max_sideways:
        pv = fringe.peek_value()

        if pv > bv:
            yield b

        if pv == bv:
            sideways_moves += 1
        else:
            sideways_moves = 0

        parents = []
        while len(fringe) > 0 and len(parents) < beam_width:
            parent = fringe.pop()
            parents.append(parent)
        fringe.clear()

        b = parents[0]
        bv = pv

        for node in parents:
            for s in problem.successors(node):
                added = True
                if not graph_search:
                    fringe.push(s)
                elif s not in closed:
                    fringe.push(s)
                    closed.add(s)
                else:
                    added = False

                if added and fringe.peek_value() <= cost_limit:
                    yield fringe.peek()


    yield b
예제 #3
0
def branch_and_bound(problem, graph=True, depth_limit=float('inf')):
    """
    An exhaustive optimization technique that is guranteed to give the best
    solution. In general the algorithm starts with some (potentially
    non-optimal) solution. Then it uses the cost of the current best solution
    to prune branches of the search that do not have any chance of being better
    than this solution (i.e., that have a node_value > current best cost).

    In this implementation, node_value should provide an admissible lower bound
    on the cost of solutions reachable from the provided node. If node_value is
    inadmissible, then optimality guarantees are lost.

    Also, if the search space is infinite and/or the node_value function
    provides too little guidance (e.g., node_value = float('-inf')), then
    the search might never terminate. To counter this, a depth_limit can be
    provided that stops expanding nodes after the provided depth. This will
    ensure the search is finite and guaranteed to terminate.

    Finally, the problem.goal_test function can be used to terminate search
    early if a good enough solution has been found. If goal_test(node) return
    True, then search is immediately terminated and the node is returned.

    Note, the current implementation uses best-first search via a priority
    queue data structure.

    :param problem: The problem to solve.
    :type problem: :class:`py_search.base.Problem`
    :param graph: Whether to use graph search (no duplicates) or tree search
                  (duplicates).
    :type graph: Boolean
    """
    b = None
    bv = float('inf')

    fringe = PriorityQueue(node_value=problem.node_value)
    fringe.push(problem.initial)

    if graph:
        closed = set()
        closed.add(problem.initial)

    while len(fringe) > 0:
        pv = fringe.peek_value()

        if bv < pv:
            break

        node = fringe.pop()

        if problem.goal_test(node, problem.goal):
            yield SolutionNode(node, problem.goal)

        if problem.node_value(node) < bv:
            b = node
            bv = problem.node_value(node)
            fringe.update_cost_limit(bv)

        if depth_limit == float('inf') or node.depth() < depth_limit:
            for s in problem.successors(node):
                if not graph:
                    fringe.push(s)
                elif s not in closed:
                    fringe.push(s)
                    closed.add(s)

    yield SolutionNode(b, problem.goal)
예제 #4
0
def local_beam_search(problem, beam_width=1, max_sideways=0, graph=True):
    """
    A variant of :func:`py_search.informed_search.beam_search` that can be
    applied to local search problems.  When the beam width of 1 this approach
    yields behavior similar to :func:`hill_climbing`.

    The problem.goal_test function can be used to terminate search early if a
    good enough solution has been found. If goal_test(node) return True, then
    search is immediately terminated and the node is returned.

    :param problem: The problem to solve.
    :type problem: :class:`py_search.base.Problem`
    :param beam_width: The size of the search beam.
    :type beam_width: int
    :param max_sideways: Specifies the max number of contiguous sideways moves.
    :type max_sideways: int
    :param graph: Whether to use graph search (no duplicates) or tree
        search (duplicates)
    :type graph: Boolean
    """
    b = None
    bv = float('inf')
    sideways_moves = 0

    fringe = PriorityQueue(node_value=problem.node_value)
    fringe.push(problem.initial)

    while len(fringe) < beam_width:
        fringe.push(problem.random_node())

    if graph:
        closed = set()
        closed.add(problem.initial)

    while len(fringe) > 0 and sideways_moves <= max_sideways:
        pv = fringe.peek_value()

        if bv < pv:
            yield SolutionNode(b, problem.goal)

        if pv == bv:
            sideways_moves += 1
        else:
            sideways_moves = 0

        parents = []
        while len(fringe) > 0 and len(parents) < beam_width:
            parent = fringe.pop()
            parents.append(parent)
        fringe.clear()

        b = parents[0]
        bv = pv

        for node in parents:
            if problem.goal_test(node, problem.goal):
                yield SolutionNode(node, problem.goal)

            for s in problem.successors(node):
                if not graph:
                    fringe.push(s)
                elif s not in closed:
                    fringe.push(s)
                    closed.add(s)

    yield SolutionNode(b, problem.goal)