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
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
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)
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)