Weeks 01-02: Searching the State Space

State Space

State: object representing a possible configuration of the world (agent and environment).

State space: set of all possible states (cross product of all elements of the state).

State Space Graphs

Directed Graph

Many problems can be abstracted into the problem of finding a path in a directed graphs.

Explicit Graphs

Implicit Graphs

Searching Graphs

Generic algorithm

def search(graph, start_nodes, is_goal_node):
  frontier = [[s] for s in start_nodes]
  while len(frontier) != 0:
    path = frontier.pop()
    if is_goal_node[path[-1]]:
      return path

  for n in path[-1].neighbors():
    frontier.append(path + n)

DFS

Explicit graphs

Tracing the frontier:

BFS

Lowest-cost-first search (LCFS)

Priority Queue

Pruning

Two problems: cycles - an infinite search tree, and when expanding multiple paths leads to the same node.

We need memory - the frontier should keep track of which nodes have been expanded/closed.

Prune when:

LCFS finds an optimal solution, but it explores options in every direction, and knows nothing about the goal location.

Heuristics

Extra knowledge that can be used to guide a search:

A* Search Strategy

f(p)=cost(p)+h(n)f(p) = cost(p) + h(n) where:

Monotonicity

A requirement that is stronger than admissibility. A function is monotone/consistent if, for any two nodes nn and nn' (which are reachable from nn):

h(n)cost(n,n)+h(n) h(n) \leq cost(n, n') + h(n')

That is, the estimated cost from nn to the goal must be less than the estimated cost of going to the goal via nn'. Where ss is the start node:

f(n)=cost(s,n)+h(n)=cost(s,n)+cost(n,n)+h(n)f(n)cost(s,n)+h(n)f(n)f(n) \begin{aligned} f(n') &= cost(s, n') + h(n') \\ &= cost(s, n) + cost(n, n') + h(n') \\ \therefore f(n') &\leq cost(s, n) + h(n) \\ \therefore f(n') &\leq f(n) \\ \end{aligned}

Thus, f(n)f(n) is non-decreasing along any path.

Finding good heuristics

Solve a simpler version of the problem:

Sliding puzzle example:

Dominance Relation

Dominance: for two heuristics, if ha>hch_a > h_c if n:ha(n)hc(n)\forall{n}: h_a(n) \geq h_c(n).

Heuristics form a semi-lattice: if neither are dominating, could use max(ha(n),hc(n))max(h_a(n), h_c(n)).

The bottom of the lattice is the zero heuristic - makes A* search just a LCFS.

Takes a bound (cost or depth) and does not expand paths that exceed the bound:

Uses less memory but more CPU when compared to BFS:

Complexity with solution at depth kk and branching factor bb:

Level BFS Iterative Deepening Num. Nodes
11 11 (only looks at nodes once) kk (repeat search kk times) bb
22 11 k1k - 1 b2b^2
kk 11 11 bkb^k
Total bk\geq b^k bk(bb1)2\leq b^k(\frac{b}{b-1})^2

As branching factor increases, complexity gets closer and closer to BFS - thus, it is not very wasteful.