CPSC 322 - Lecture 13 - October 6, 2004

CPSC 322 - Lecture 13

Search


I.  One more assumption

As noted in your recent midterm exam, work in artificial
intelligence has gone forward under a handful of assumptions:

  Whatever intelligence is, it results from some kind of
  computation and it's platform independent.  It's not
  unique to brains.

  Symbol manipulation is a type of computation that is
  sufficient to give rise to intelligent behavior.

  Any symbol manipulation can be carried out on a Turing
  machine.

Prepare to hang another foundational assumption on the
wall where you've put the others.  This one comes from
the same folks who brought you the second assumption 
above...they're both part of the Physical Symbol System 
Hypothesis:

  A physical symbol system exercises its intelligence in 
  problem solving by search -- that is, by generating and 
  progressively modifying symbol structures until it
  produces a solution structure.

  Allen Newell and Herbert A. Simon, "Computer Science
  as Empirical Inquiry: Symbols and Search"

The upshot of this is that lots of people in AI believe
that most, if not all, problems can be mapped onto some
sort of representation that looks like a graph and that
those problems can be solved by some variation of a search
algorithm.


II. The state space

The metaphor of searching a graph is also a convenient one for 
describing the state of a process (i.e., a program in 
execution).  The state of a process changes over time, and at 
any given time the state of a process is a little slice of 
its history.

At a very low level, the state of a process is described by 
the values of the arguments being passed, the instruction 
being executed, and if you're programming with side effects, 
the bindings of variables to values.  (Obviously, it's easier 
to describe the state of a process if you don't have to worry 
about side effects, as there's just that much less to keep 
track of.)  However, thinking about state at this low level 
becomes very tedious very quickly.  So, we might be better 
off using a higher-level abstraction in thinking about the 
state of a process.  Consider, for example, a program to 
solve the 8-tile puzzle.  Instead of thinking in terms of 
which instruction is being executed, the values bound to 
arguments, and so on, we can look at the process in terms of 
the state of the puzzle itself.  Thus, the initial state of 
the process would be the initial state of the puzzle.  Say 
the initial state looks like this:

                  2 8 3
                  1 6 4
                  7   5

We could move any of three tiles, the 7, the 6, or the 5, to 
generate the three possible next states from this one:

                  2 8 3
                  1 6 4
                  7   5
                   /|\
                  / | \
                 /  |  \
                /   |   \
               /    |    \
              /     |     \
             /      |      \
            /       |       \
          2 8 3   2 8 3   2 8 3
          1 6 4   1   4   1 6 4
            7 5   7 6 5   7 5

If we then choose, say, the lower leftmost state of those 
three new states, and generate the two possible next states 
from that one, we get this:

                  2 8 3
                  1 6 4
                  7   5
                   /|\
                  / | \
                 /  |  \
                /   |   \
               /    |    \
              /     |     \
             /      |      \
            /       |       \
          2 8 3   2 8 3   2 8 3
          1 6 4   1   4   1 6 4
            7 5   7 6 5   7 5
           / \
          /   \
         /     \
        /       \
       /         \
     2 8 3     2 8 3
       6 4     1 6 4
     1 7 5     7   5

Note that one of these new states is just a repeat of the 
initial state.  We wouldn't want to explore that direction 
any further, because we'd just be doing work we've already 
done.

Now, if we think of the movement of tiles as the significant 
operators in this process, we can describe the history of 
the process in terms of puzzle boards and the operators
necessary to get from one board to the next.  And since the 
nature of the operators in this case are such that only at 
most four new boards can be generated from any given board, 
we can safely say that the current behavior of the process 
depends on its history--the process couldn't have been in the 
current state without having just been in one of a very few 
previous states.

A state-space is defined as the set of all possible states 
generated by the repeated application of a finite set of 
operators (or operations, or transformations, or moves...they're
all the same thing in this context) to some initial state.  In 
performing a state-space search, the intention is usually to find a 
sequence of operators that gets one from the initial state to some goal 
state.  In the case of the 8-tile puzzle, that goal state 
might be:

                  1 2 3
                  8   4
                  7 6 5

The state space can be generated in advance, as is the case
with the examples in our textbook as well as some of the 
examples we'll be doing in class.  But it's also possible
to build just the state space that's needed for exploration
as the search process proceeds.

Why generate the state space at run-time, and not just have 
it all built in advance?  For some applications, that might 
not be much of a problem.  For example, in the 8-tile puzzle, 
the number of different ways to arrange the tiles isn't 
overwhelming.  At most, it might be around 9!, before subtracting
impossible states.  On the other hand, if you were working on a 
program that could play a decent game of chess, and you 
wanted to pre-build a data structure that was comprised of 
all possible boards, you'd want to make sure that you set 
aside a little disk space to store the approximately 10^120  
(i.e., 1,000,000,000,000,000,000,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,000,000,000,000,000) different 
boards that are possible.  Or maybe you'd be better off 
writing your program to generate just those boards that were 
relevant to the specific chess game it was playing at that 
particular time, and not worry about the rest of them.

For now, though, our problems will have pre-built state
spaces, and we'll worry about generating the state space 
as we go in later lectures.


III. Examples of state-space search in the real world

The state-space search is used in a lot of ways by lots of folks.
For example, a compiler has a component called a parser which 
decomposes a high-level instruction into its component parts.  
But these instructions can be ambiguous, so the parser must 
make decisions about how various symbols (known as "tokens" 
in compiler world) are being used.  How that decision is made 
depends on what the parser has already seen; in other words, 
the next possible state of the parsing process depends on the 
history of the previous states.

The parser reads the input from left to right, making 
"guesses" as it goes.  If the sequence of guesses leads to a 
structure for an instruction that's not legal, the parser 
will backtrack and systematically try new guesses, just like 
a depth-first search algorithm.  If no combination of guesses 
works for the parser, you'll get a "syntax error" message.  
These things are sometimes called "recursive descent 
parsers", and you'll get to study these in your compiler 
course, if you're brave enough to volunteer for it.

The same sorts of ideas are used to get computers to 
understand English and other natural languages.  In fact, an 
entire company was founded on this idea.  A guy named Gary 
Hendrix at the University of Texas wrote a PhD thesis on 
parsing English back in the late 60's or early 70's.  He 
later took some of those same ideas and built an interface to 
a simple database system -- an interface that could accept 
data base queries in English (or at least a subset of 
English).  He called the whole thing "Q&A", it ran on PC 
compatibles, and it sold off the shelf at computer stores 
for about $300 a copy.  This product was one of the first, if 
not the first, offered by the company Hendrix founded, which 
is called "Symantec" -- a company which most of you Mac 
or PC owners know about, since it has swallowed up all sorts 
of other software vendors.  Hendrix is now a zillionaire, and 
the moral to this story is that state-space search can make 
you rich.

Evolutionary biologists think of all of us as the bottom layers 
of nodes on a very big state space.  Those of us who don't have 
any children are the leaves on a very very big tree (well, 
it's not exactly a tree, but you get the idea).  Some of us 
will generate new states (our kids) and others of us won't.  
Each state presumably brings humanity slightly closer to some 
lifeform that is perfectly adapted to the environment.  (If 
only we could get the environment to stop changing....)

Finally, as we demonstrated via our Calvin and Hobbes 
example, state-space search is a nice little metaphor for how 
we lead our lives:  every decision we make is based on the 
chain of decisions leading up to that point.  As Calvin and
Hobbes illustrated however, in life, unlike in your computer, 
there's often no backtracking possible when you make a bad decision.


IV.  A first pass at the generic search algorithm

So let's say you have a problem that maps onto a state
space (don't we all?) and that the state space can be
represented as a graph.  In that graph, the individual
nodes or states will represent partial solutions to the
problem, the arcs between nodes will represent transformations
from one partial solution to another, and the mission of the
search procedure is to find a path along the arcs from some
start node to some goal node.

A high-level, hand-wavy description of an algorithm that
carries out the search mission looks like this:

Given a set of start nodes, a set of goal nodes, 
and a graph:

  make a "list" of the start nodes - let's call it
  the "frontier"

  repeat 
    if no nodes on the frontier then terminate with failure
    choose one node from the frontier and remove it
    if the chosen node matches the goal node
      then terminate with success
      else put next nodes (the neighbors of the chosen node)
        on the frontier
  end repeat

Once you have a nice search procedure like this, you can
start solving simple problems like the 8-tile puzzle in 
an exhaustive and not-very-intelligent way.  That's the
example that you'll see in the powerpoint slides.  

After that example, we asked some important questions
that we'll attempt to answer in the days to come.

Last revised: October 26, 2004