def breadth_first_search(planning_task):
    """
    Searches for a plan on the given task using breadth first search and
    duplicate detection.

    @param planning_task: The planning task to solve.
    @return: The solution as a list of operators or None if the task is
    unsolvable.
    """
    # counts the number of loops (only for printing)
    iteration = 0
    # fifo-queue storing the nodes which are next to explore
    queue = deque()
    queue.append(searchspace.make_root_node(planning_task.initial_state))
    # set storing the explored nodes, used for duplicate detection
    closed = {planning_task.initial_state}
    while queue:
        iteration += 1
        logging.debug("breadth_first_search: Iteration %d, #unexplored=%d" %
                      (iteration, len(queue)))
        # get the next node to explore
        node = queue.popleft()
        # exploring the node or if it is a goal node extracting the plan
        if planning_task.goal_reached(node.state):
            logging.info("Goal reached. Start extraction of solution.")
            logging.info("%d Nodes expanded" % iteration)
            return node.extract_solution()
        for operator, successor_state in planning_task.get_successor_states(
                node.state):
            # duplicate detection
            if successor_state not in closed:
                queue.append(
                    searchspace.make_child_node(node, operator,
                                                successor_state))
                # remember the successor state
                closed.add(successor_state)
    logging.info("No operators left. Task unsolvable.")
    logging.info("%d Nodes expanded" % iteration)
    return None
"""
Unit Testing for the search space module
"""

from pyperplan.search.searchspace import make_child_node, make_root_node

# Construct a small tree in order to perform some needed test methods

root = make_root_node("state1")
child1 = make_child_node(root, "action1", "state2")
child2 = make_child_node(root, "action2", "state3")
grandchild1 = make_child_node(child1, "action3", "state4")
grandchild2 = make_child_node(child2, "action4", "state5")


def test_extract_solution():
    """
    Tests whether extract_solution method within class searchspace returns the
    list of actions starting from the root
    """
    assert root.extract_solution() == []
    assert grandchild1.extract_solution() == ["action1", "action3"]
    assert grandchild2.extract_solution() == ["action2", "action4"]


def test_g_values():
    """
    Tests whether the distance of the node from the root is computed properly
    """
    assert root.g == 0
    assert child1.g == 1
Example #3
0
finisher1-stack-letter sheet1 dummy-sheet
"""
optimal_plan = [op.strip() for op in optimal_plan.splitlines()]

import os

from pyperplan import planner
from pyperplan.search import breadth_first_search, searchspace
from pyperplan.task import Operator, Task

benchmarks = os.path.abspath(
    os.path.join(os.path.abspath(__file__), "../../../../benchmarks"))

# Collect problem files
problem_file = os.path.join(benchmarks, "parcprinter", "task01.pddl")
domain_file = planner.find_domain(problem_file)

problem = planner._parse(domain_file, problem_file)
task = planner._ground(problem)

# Manually do the "search"
node = searchspace.make_root_node(task.initial_state)
for step, op_name in enumerate(optimal_plan, start=1):
    for op, successor_state in task.get_successor_states(node.state):
        if not op.name.strip("()") == op_name:
            continue
        node = searchspace.make_child_node(node, op, successor_state)

# Check that we reached the goal
assert len(task.goals - node.state) == 0
Example #4
0
def astar_search(
        task,
        heuristic,
        make_open_entry=ordered_node_astar,
        use_relaxed_plan=False,
        max_search_time=float("inf"),
):
    """
    Searches for a plan in the given task using A* search.

    @param task The task to be solved
    @param heuristic  A heuristic callable which computes the estimated steps
                      from a search node to reach the goal.
    @param make_open_entry An optional parameter to change the bahavior of the
                           astar search. The callable should return a search
                           node, possible values are ordered_node_astar,
                           ordered_node_weighted_astar and
                           ordered_node_greedy_best_first with obvious
                           meanings.
    @param max_search_time Maximum search time in seconds
    """
    open = []
    state_cost = {task.initial_state: 0}
    node_tiebreaker = 0

    root = searchspace.make_root_node(task.initial_state)
    init_h = heuristic(root)
    heapq.heappush(open, make_open_entry(root, init_h, node_tiebreaker))
    _log.info("Initial h value: %f" % init_h)

    besth = float("inf")
    counter = 0
    expansions = 0

    # Number of heuristic calls, include the initial call for root note
    heuristic_calls = 1

    # Used so we can interrupt the search
    start_time = time.perf_counter()

    while open:
        # Check whether max search time exceeded
        elapsed_time = time.perf_counter() - start_time
        if elapsed_time >= max_search_time:
            metrics = SearchMetrics(
                nodes_expanded=expansions,
                plan_length=-1,
                heuristic_calls=heuristic_calls,
                heuristic_val_for_initial_state=init_h,
                search_time=elapsed_time,
                search_state=SearchState.timed_out,
            )
            _log.warning("Search timed out")
            _log.info("%d Nodes expanded" % expansions)
            _log.info("%d times heuristic called" % heuristic_calls)
            return [], metrics

        (f, h, _tie, pop_node) = heapq.heappop(open)
        if h < besth:
            besth = h
            _log.debug("Found new best h: %d after %d expansions" %
                       (besth, counter))

        pop_state = pop_node.state

        # Only expand the node if its associated cost (g value) is the lowest
        # cost known for this state. Otherwise we already found a cheaper
        # path after creating this node and hence can disregard it.
        if state_cost[pop_state] == pop_node.g:
            expansions += 1

            if task.goal_reached(pop_state):
                _log.info("Goal reached. Start extraction of solution.")
                _log.info("%d Nodes expanded" % expansions)
                _log.info("%d times heuristic called" % heuristic_calls)
                sol = pop_node.extract_solution()

                # Create metrics
                metrics = SearchMetrics(
                    nodes_expanded=expansions,
                    plan_length=len(sol),
                    heuristic_calls=heuristic_calls,
                    heuristic_val_for_initial_state=init_h,
                    search_time=time.perf_counter() - start_time,
                    search_state=SearchState.success,
                )
                return sol, metrics

            rplan = None
            if use_relaxed_plan:
                (rh, rplan) = heuristic.calc_h_with_plan(
                    searchspace.make_root_node(pop_state))
                _log.debug("relaxed plan %s " % rplan)

            for op, succ_state in task.get_successor_states(pop_state):
                if use_relaxed_plan:
                    if rplan and not op.name in rplan:
                        # ignore this operator if we use the relaxed plan
                        # criterion
                        _log.debug("removing operator %s << not a "
                                   "preferred operator" % op.name)
                        continue
                    else:
                        _log.debug("keeping operator %s" % op.name)

                succ_node = searchspace.make_child_node(
                    pop_node, op, succ_state)

                old_succ_g = state_cost.get(succ_state, float("inf"))
                if succ_node.g < old_succ_g:
                    h = heuristic(succ_node)
                    heuristic_calls += 1

                    if h == float("inf"):
                        # don't bother with states that can't reach the goal anyway
                        continue

                    # We either never saw succ_state before, or we found a
                    # cheaper path to succ_state than previously.
                    node_tiebreaker += 1
                    heapq.heappush(
                        open, make_open_entry(succ_node, h, node_tiebreaker))
                    state_cost[succ_state] = succ_node.g

        counter += 1
    _log.info("No operators left. Task unsolvable.")
    _log.info("%d Nodes expanded" % expansions)

    # Create metrics
    metrics = SearchMetrics(
        nodes_expanded=expansions,
        plan_length=-1,
        heuristic_calls=heuristic_calls,
        heuristic_val_for_initial_state=init_h,
        search_time=time.perf_counter() - start_time,
        search_state=SearchState.failed,
    )
    return None, metrics
Example #5
0
def enforced_hillclimbing_search(planning_task,
                                 heuristic,
                                 use_preferred_ops=False):
    """
    Searches for a plan on the given task using enforced hill climbing and
    duplicate detection.

    @param planning_task: The planning task to solve.
    @return: The solution as a list of operators or None if no solution was
    found. Note that enforced hill climbing is an incomplete algorith, so it
    may fail to find a solution even though the task is solvable.
    """
    # counts the number of loops (only for printing)
    iteration = 0
    # fifo-queue storing the nodes which are next to explore
    queue = deque()
    initial_node = searchspace.make_root_node(planning_task.initial_state)
    queue.append(initial_node)
    best_heuristic_value = heuristic(initial_node)
    logging.info("Initial h value: %f" % best_heuristic_value)
    # set storing the explored nodes, used for duplicate detection
    closed = set()
    visited = set()
    while queue:
        iteration += 1
        # get the next node to explore
        node = queue.popleft()
        # remember the successor state
        closed.add(node.state)
        visited.add(node.state)
        # exploring the node or if it is a goal node extracting the plan
        if planning_task.goal_reached(node.state):
            logging.info("Goal reached. Start extraction of solution.")
            logging.info("%d Nodes expanded" % len(visited))
            return node.extract_solution()

        # for the preferred operator version --> recompute heuristic and
        # relaxed plan
        if use_preferred_ops:
            (rh, rplan) = heuristic.calc_h_with_plan(node)
            logging.debug("relaxed plan %s " % rplan)

        for operator, successor_state in planning_task.get_successor_states(
                node.state):

            # for the preferred operator version ignore all non preferred
            # operators
            if use_preferred_ops:
                if rplan and not operator.name in rplan:
                    # ignore this operator if we use the relaxed plan criterion
                    logging.debug("removing operator %s << not a preferred "
                                  "operator" % operator.name)
                    continue
                else:
                    logging.debug("keeping operator %s" % operator.name)

            # duplicate detection
            if successor_state not in closed:
                successor_node = searchspace.make_child_node(
                    node, operator, successor_state)
                heuristic_value = heuristic(successor_node)
                if heuristic_value == float("inf"):
                    continue
                elif heuristic_value < best_heuristic_value:
                    # Just take the first successor node that has a lower
                    # heuristic value than the current best_heuristic_value
                    # and ignore the other successor nodes.
                    logging.debug("Found new best h: %f after %d expansions" %
                                  (heuristic_value, iteration))
                    queue.clear()
                    closed.clear()
                    best_heuristic_value = heuristic_value
                    queue.append(successor_node)
                    break
                else:
                    queue.append(successor_node)
    logging.info("Enforced hill climbing failed")
    logging.info("%d Nodes expanded" % len(visited))
    return None