Topics:
States
Definition of State of a computation:
a set of information sufficient to determine the future possible behaviors
A state also serves the role of abstracting past behaviors, in particular the behaviors which can get it to the state in question
A typical computational system:
This is obviously an over-simplification.
Objections include:
Our answer to this objection is that in many cases the system can be viewed as if the input were there from the beginning and just not looked at all at once.
For example, the input could be on a "tape" which is scanned sequentially. This tape could correspond to:
Our answer to this objection is that it is partly valid; such systems do not reach a final state, but they do reach intermediate states which mark the end of specific transactions (with the entire output of a transaction having been produced).
Having noted the objections, we still feel justified in declaring systems which are intended to reach a final state as "typical".
The notion of state can be clarified by studying various puzzles. These have state transition behavior, but typically not input and output.
e.g. Towers of Hanoi Puzzle
A state is the assignment of a set of disks to one of three spindles:
Shown here is the initial state for 4 disks:
The desired final state is:
A state in this puzzle must be "legal": A large disk cannot be placed atop a smaller one:
Illegal (not an allowable state):
In the abstract version of the puzzle, every disk must be on some spindle.
(If we were programming a graphical demo, in which the movement of disks is shown, there is a more detailed state which would include the disk's spatial position during transit.)
A transition is a single-step change from one state to another. Call the states the pre- state and the post- state of the transition.
For computational systems and puzzles there are constraints on what constitutes a transition.
In the Towers of Hanoi:
Since a transition is a pair of states
[pre-, post]
the set of all transitions is a binary relation (set of pairs. As such, the set can be represented by a directed graph:
For the Towers of Hanoi, this graph depends on the number of disks. For two disks it looks like:
If there were only a single disk, then there would be only three states.
How many states would there be for an N-disk puzzle?
Is this a lot?
rex > map(pow(3), from(1));
[3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 59049, 177147, 531441, 1594323, 4782969, 14348907, 43046721, 129140163, 387420489, 1162261467, 3486784401, 10460353203, 31381059609, 94143178827, 282429536481
For 10 disks there are 59049 states.
For 24 disks, 282429536481states.
The pattern of transitions as N -> infinity
yields some interesting artwork.
Transition pattern for 1 disk:
Transition pattern for 2 disk:
To go from N disks to N+1 disks:
Each node in the N disk case becomes 3 nodes in the N+1 disk case. The connections are "resoldered" thus:
For example, the diagram for 3 disks:
The limiting case is a self-similar figure or fractal, in this case one resembling the Sierpinski gasket.
To see the skeleton of this pattern plotted for successive values of N, run on muddcs (under X):
rex /cs/cs60/rex/special/sierpinski.rex
The transition relation shows single step changes between states. The reachability relation is the transitive closure of the transition relation:
T0 ==> Tn
means that there are terms T1, T2, ...., Tn-1 (n > 0) such that
T0 => T1 => T2 => .... => Tn-1 => Tn
Deterministic
vs. Non-Deterministic systems
A state-transition system is called:
Non-deterministic behavior in the state diagram
Puzzles are typically non-deterministic
(otherwise they wouldn't be puzzling)
Computational systems are typically deterministic (although there are definite exceptions)
Transition
rules and the
sol program
It is desirable to be able to represent transitions without drawing a diagram:
The number of states may be
We want a transition rule notation which will represent a large (or infinite) number of transitions at one time.
The rex rule notation is one such, for deterministic systems.
For non-deterministic systems, we can use a similar notation, but
take away the requirement that only the first applicable rule in a sequence can be used
Example: Towers of Hanoi puzzle
Represent a state as a pattern:
towers(L, M, N)
where L, M, and N are lists of disk numbers
(assume the disks are numbered
[1, 2, 3, ...., N]
e.g. initial state (for 4-disks) is
towers([1, 2, 3, 4], [ ], [ ])
final state is
towers([ ], [ ], [1, 2, 3, 4])
Represent rules as:
towers(L, M, N) => towers(L', M', N').
or as
towers(L, M, N) => Condition ? towers(L', M', N').
where L, M, N, ... are variables or patterns involve lists (with variables).
% rule 12e
towers([A | L], [], N) =>
towers( L, [A], N).
% rule 12n
towers([A | L], [B | M], N) => A < B ?
towers( L, [A, B | M], N).
(12e stands for spindle 1 to spindle 2 empty.)
We can represent all the transitions of the puzzle (for arbitrary N) using 12 transition rules.
inputs such rules and a pair of initial and final states to determine if there is a solution.
Examples, including towers.sol may be found in
To run these examples, tell UNIX:
sol
After the prompt
| ?-
enter
consult('towers.sol').
to load the towers example. Note that sol is different from rex:
To generate the solution, enter
solve.
for the a depth-first solution, or
solve_bf.
for a breadth-first solution.
Breadth-first solutions may take longer to
generate, but are guaranteed to have the
shortest length among
all solutions for a given pair of initial and final state.
Why is this?
Other puzzle examples (try coding their
rules).
See the notes for a full description.
We have already described how to think of a low-level functional program in terms of "states":
Now we shift to imperative prog
rams, in which the meaning is usually viewed in terms of commands, especially assignment of values to variables representing abstract memory.
We'll use the C++ syntax for assignment:
LHS = RHS;
where LHS is typically a variable and RHS is an expression.
More generally,
RHS is an expression which yields an
R-value
(a value which can be stored in a memory location),
while
LHS is an expression which yields an
L-value
(a value which is a memory location),
Example:
int sum, i, n;
n = .... input value ....;
sum = 0;
i = 0;
while( i < n )
{
sum = sum + i;
i = i + 1;
}
.... output .... = sum;
What are the states in such a program?
The states can be represented as an n-tuple, with
a "slot" corresponding to each variable, plus a special variable
ip (instruction
pointer) which designates a point in the program before the next
"instructions" e.g. for the above program, the values of
(ip, n, i, sum)
We can give a number to the possible values of ip for convenience. We annotate the program with those numbers:
int sum, i, n;
n = .... input value ....;
/* ip = 0 */
sum = 0;
/* ip = 1 */
i = 0;
while( /* ip = 2 */ i < n )
{
sum = sum + i;
/* ip = 3 */
i = i + 1;
}
/* ip = 4 */
.... output .... = sum;
Now we can state
transition rules for this program:
Recall that the state is of the form
(ip, n, i, sum)
so the rules are:
(0, n, i, sum) => (1, n, i, 0);
(1, n, i, sum) => (2, n, 0, sum);
(2, n, i, sum) => i < n ? (3, n, i, sum+i);
(2, n, i, sum) => !(i < n) ? (4, n, i, sum);
(3, n, i, sum) => (2, n, i+1, sum);
Let's check these rules with an example, where n = 4 initially:
(ip, n, i, sum)
(0, 4, ?, ?) =>
(1, 4, ?, 0) =>
(2, 4, 0, 0) =>
(3, 4, 0, 0) =>
(2, 4, 1, 0) =>
(3, 4, 1, 1) =>
(2, 4, 2, 1) =>
(3, 4, 2, 3) =>
(2, 4, 3, 3) =>
(3, 4, 3, 6) =>
(2, 4, 4, 6) =>
(4, 4, 4, 6)
Now no rule is applicable.
The output of the program is the value of sum, which is 6.
We can make these rules into a nicer-looking
functional program by replacing the ip values with separate function
names:
(0, n, i, sum) => (1, n, i, 0);
(1, n, i, sum) => (2, n, 0, sum);
(2, n, i, sum) => i < n ? (3, n, i, sum+i);
(2, n, i, sum) => !(i < n) ? (4, n, i, sum);
(3, n, i, sum) => (2, n, i+1, sum);
becomes
f0(n, i, sum) => f1(n, i, 0);
f1(n, i, sum) => f2(n, 0, sum);
f2(n, i, sum) => i < n ? f3(n, i, sum+i);
f2(n, i, sum) => !(i < n) ? f4(n, i, sum);
f3(n, i, sum) => f2(n, i+1, sum);
To make this attractive for someone to use, we provide an interface function:
f(n) = f0(n, arb, arb);
where arb is any arbitrary value. We also include a way to get the answer:
f4(n, i, sum) = sum;
The above functional program can be executed:
No assignment is actually required; it is "simulated" by the way we manipulate argument values.
We call this
McCarthy's Transformation
after John McCarthy, the inventor of Lisp.
A corollary is that recursion is at least as fundamental as assignment, as far as computational results are concerned.
You should be able to carry out McCarthy's
transformation on simple imperative programs.
You need not go through the "ip" stage.
The notes describe a flowchart viewpoint.
Components of a Turing Machine (TM):
One symbol in the alphabet is distinguished as "blank". The distinction of blank is that, in any given state, at most a finite set of the cells will contain other than blank.
Depiction of a TM with controller in state q
The controller, in a given state, will:
A description of, or
program for, a TM is a
partial function, called the transition function:
States x Symbols -> Symbols x Motions x States
where Motions = {left, right, none}
The symbol x here denotes the Cartesian product of sets.
In the present case, we are saying that the transition function of a TM is a partial function having a domain consisting of all pairs of the form
(State, Symbol)
and a co-domain consisting of all 3-tuples (triples) of the form
(Symbol, Motion, States)
Turing Machine Addition Example
A machine which adds 1 to its binary input numeral. It is assumed that the machine starts with the head to the immediate right of the least significant bit, which is on the right:
_ _ _ _ _ 1 0 1 1 0 1 1 1 1 1 _ _ _ _ _ _
^
The state transitions for such a machine could be specified as:
State Read Written Motion NewState
start _ _ left add1
add1 0 1 right end
add1 _ 1 right end
add1 1 0 left add1
end 0 0 right end
end 1 1 right end
Execution Example
1 0 0 1 1 1 [_] [start]
1 0 0 1 1 [1] _ [add1]
1 0 0 1 [1] 0 _ [add1]
1 0 0 [1] 0 0 _ [add1]
1 0 [0] 0 0 0 _ [add1]
1 0 1 [0] 0 0 _ [end]
1 0 1 0 [0] 0 _ [end]
1 0 1 0 0 [0] _ [end]
1 0 1 0 0 0 [_] [end]