def beam_search(problem, beam_width=1, graph=True): """ A variant of breadth-first search where all nodes in the fringe are expanded, but the resulting new fringe is limited to have length beam_width, where the nodes with the worst value are dropped. The default beam width is 1, which yields greedy best-first search (i.e., hill climbing). There are different ways to implement beam search, namely best-first beam search and breadth-first beam search. According to: Wilt, C. M., Thayer, J. T., & Ruml, W. (2010). A comparison of greedy search algorithms. In Third Annual Symposium on Combinatorial Search. breadth-first beam search almost always performs better. They find that allowing the search to re-expand duplicate nodes if they have a lower cost improves search performance. Thus, our implementation is a breadth-first beam search that re-expand duplicate nodes with lower cost. :param problem: The problem to solve. :type problem: :class:`Problem` :param beam_width: The size of the beam (defaults to 1). :type beam_width: int :param graph_search: whether to use graph or tree search. :type graph_search: boolean """ closed = {} fringe = PriorityQueue(node_value=problem.node_value) fringe.push(problem.initial) closed[problem.initial] = problem.initial.cost() while len(fringe) > 0: parents = [] while len(fringe) > 0 and len(parents) < beam_width: parent = fringe.pop() if problem.goal_test(parent, problem.goal): yield SolutionNode(parent, problem.goal) parents.append(parent) fringe.clear() for node in parents: for s in problem.successors(node): if not graph: fringe.push(s) elif s not in closed or s.cost() < closed[s]: fringe.push(s) closed[s] = s.cost()
def beam_search(problem, beam_width=1, graph_search=True): """ A variant of breadth-first search where all nodes in the fringe are expanded, but the resulting new fringe is limited to have length beam_width, where the nodes with the worst value are dropped. The default beam width is 1, which yields greedy best-first search (i.e., hill climbing). There are different ways to implement beam search, namely best-first beam search and breadth-first beam search. According to: Wilt, C. M., Thayer, J. T., & Ruml, W. (2010). A comparison of greedy search algorithms. In Third Annual Symposium on Combinatorial Search. breadth-first beam search almost always performs better. They find that allowing the search to re-expand duplicate nodes if they have a lower cost improves search performance. Thus, our implementation is a breadth-first beam search that re-expand duplicate nodes with lower cost. :param problem: The problem to solve. :type problem: :class:`Problem` :param beam_width: The size of the beam (defaults to 1). :type beam_width: int :param graph_search: whether to use graph or tree search. :type graph_search: boolean """ closed = {} fringe = PriorityQueue(node_value=problem.node_value) fringe.push(problem.initial) closed[problem.initial] = problem.initial.cost() while len(fringe) > 0: parents = [] while len(fringe) > 0 and len(parents) < beam_width: parent = fringe.pop() if problem.goal_test(parent): yield parent parents.append(parent) fringe.clear() for node in parents: for s in problem.successors(node): if not graph_search: fringe.push(s) elif s not in closed or s.cost() < closed[s]: fringe.push(s) closed[s] = s.cost()
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 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)