Notes on Models of Computation
Chapter 9
H. Conrad Cunningham
06 April 2022
Browser Advisory: The HTML version of this textbook requires a browser that supports the display of MathML. A good choice as of April 2022 is a recent version of Firefox from Mozilla.
Note: These notes were written primarily to accompany my use of Chapter 1 of the Linz textbook An Introduction to Formal Languages and Automata [[1].
A finite accepter (nfa, dfa)
A pushdown accepter (npda, dpda)
has a stack for local storage
accepts a language from a larger family
The family of regular languages is a subset of the deterministic context-free languages, which is a subset of the context-free languages.
But, as we saw in Chapter 8, not all languages of interest are context-free. To accept languages like and , we need an automaton with a more flexible internal storage mechanism.
What kind of internal storage is needed to allow the machine to accept languages such as these? multiple stacks? a queue? some other mechanism?
More ambitiously, what is the most powerful automaton we can define? What are the limits of mechanical computation?
This chapter introduces the Turing machine to explore these theoretical questions. The Turing machine is a fundamental concept in the theoretical study of computation.
The Turing machine
Although Turing machines are simple mechanisms, the Turing thesis (also known as the Church-Turing thesis) maintains that any computation that can be carried out on present-day computers an be done on a Turing machine.
Note: Much of the work on computability was published in the 1930’s, before the advent of electronic computers a decade later. It included work by Austrian (and later American) logician Kurt Goedel on primitive recursive function theory, American mathematician Alonso Church on lambda calculus (a foundation of functional programming), British mathematician Alan Turing (also later a PhD student of Church’s) on Turing machines, and American mathematician Emil Post on Post machines.
Linz Figure 9.1 shows a schematic drawing of a standard Turing machine.
This deviates from the general scheme given in Chapter 1 in that the input file, internal storage, and output mechanism are all represented by a single mechanism, the tape. The input is on the tape at initiation and the output is on that tape at termination.
On each move, the tape’s read-write head reads a symbol from the current tape cell, writes a symbol back to that cell, and moves one cell to the left or right.
Turing machines were first defined by British mathematician Alan Turing in 1937, while he was a graduate student at Cambridge University.
Linz Definition 9.1 (Turing Machine): A Turing machine is defined by
where
We also require
and define
Requirement 8 means that the blank symbol cannot be either an input or an output of a Turing machine. It is the default content for any cell that has no meaningful content.
From requirement 9, we see that the arguments of the transition function are:
The result of the transition function gives:
In general, is a partial function. That is, not all configurations have a next move defined.
Consider a Turing machine with a move defined as follows:
Linz Figure 9.2 shows the situation (a) before the move and (b) after the move.
A Turing machine is a simple computer. It has
The Turing machine can
The transition function determines the behavior of the machine, i.e., it is the machine’s program.
The Turing macine starts in initial state and then goes through a sequence of moves defined by . A cell on the tape may be read and written many times.
Eventually the Turing machine may enter a configuration for which is undefined. When it enters such a state, the machine halts. Hence, this state is called a halt state.
Typically, no transitions are defined on any final state.
Consider the Turing machine defined by
,
,
,
where is defined as follows:
Linz Figure 9 .3 shows a sequence of moves for this Turing machine:
As with finite and pushdown automata, we can use transition graphs to represent Turing machines. We label the edges of the graph with a triple giving (1) the current tape symbol, (2) the symbol that replaces it, and (3) the direction in which the read-write head moves.
Linz Figure 9.4 shows a transition graph for the Turing machine given in Linz Example 9.2.
Consider the Turing machine defined in Linz Figure 9.5.
Suppose the tape initially contains with the read-write head positioned over the and in state . Then the Turing machine executes the following sequence of moves:
The machine reads symbol , leaves it unchanged, moves right (now over symbol ), and enters state .
The machine reads , leaves it unchanged, moves back left (now over again), and enters state again.
The machine then repeats steps 1-3.
Clearly, regardless of the tape configuration, this machine does not halt. It goes into an infinite loop.
Because we can define a Turing machine in several different ways, it is useful to summarize the main features of our model.
A standard Turing machine:
has a tape that is unbounded in both directions, allowing any number of left and right moves
is deterministic in that defines at most one move for each configuration
has no special input or output files. At the initial time, the tape has some specified content, some of which is considered input. Whenever the machine halts, some or all of the contents of the tape is considered output.
These definitions are chosen for convenience in this chapter. Chapter 10 (which we do not cover in this course) examines alternative versions of the Turing machine concept.
As with pushdown automata, we use instantaneous descriptions to examine the configurations in a sequence of moves. The notation (using strings)
or (using individual symbols)
gives the instantaneous description of a Turing machine in state with the tape as shown in Linz Figure 9.5.
By convention, the read-write head is positioned over the symbol to the right of the state (i.e., above).
A tape cell contains if not otherwise defined to have a value.
Example: The diagrams in Linz Figure 9.3 (above) show the instantaneous descriptions , , , and .
As with pushdown automata, we use to denote a move.
Thus, for transition rule
we can have the move
.
As usual we denote the transitive closure of move (i.e., arbitrary number of moves) using:
We also use subscripts to distinguish among machines:
Now let’s summarize the above discussion with the following definitions.
Linz Definition 9.2 (Computation): Let be a Turing machine. Then any string with and , is an instantaneous description of .
A move
is possible if and only if
.
A move
is possible if and only if
.
halts starting from some initial configuration if
for any and , for which is undefined.
The sequence of configurations leading to a halt state is a computation.
If a Turing machine does not halt, we use the following special notation to describe its computation:
Can a Turing machine accept a string ?
Yes, using the following setup:
Linz Definition 9.3 (Language Accepted by Turing Machine): Let be a Turing machine. Then the language accepted by is
.
Note: The finite string must be written to the tape with blanks on both sides. No blanks can are embedded within the input string itself.
Question: What if ?
The Turing machine might:
Any string for which the machine does not halt is, by definition, not in .
For , design a Turing machine that accepts the language denoted by the regular expression .
We use two internal states , one final state , and transition function:
,
The transition graph shown below implements this machine.
The Turing machine also halts in a final state if started in state on a blank. We could interpret this as acceptance of , but for technical reasons the empty string is not included in Linz Definition 9.3.
For , design a Turing machine that accepts
.
We can design a machine that incorporates the following algorithm:
Filling in the details, we get the following Turing machine for which:
The transitions can be broken into several sets.
The first set
replaces the leftmost with an , then causes the read-write head to travel right to the first , replacing it with a . The machine then enters a state , indicating that an has been successfully paired with a .
The second set
reverses the direction of movement until an is encountered, repositions the read-write head over the leftmost , and returns control to the initial state.
The machine is now back in the initial state , ready to process the next - pair.
After one pass through this part of the computation, the machine has executed the partial computation:
So, it has matched a single with a single .
The machine continues this process until no is found on leftward movement.
If all ’s have been replaced, then state detects a instead of an and changes to state . This state must verify that all ’s have been processed as well.
The input makes the moves shown below. (The bold number in parenthesis gives the rule applied in that step.)
– start at left end | |||
(1) | – process 1st a-b pair | ||
(2) | – moving to right | ||
(4) | |||
(6) | – move back to left | ||
(7) | |||
(1) | – process 2nd a-b pair | ||
(3) | – moving to right | ||
(4) | |||
(5) | – move back to left | ||
(7) | |||
(8) | – no a’s | ||
(9) | – check for extra b’s | ||
(10) | – done, move to final |
The Turing machine halts in final state , thus accepting the string .
If the input is not in the language, the Turing machine will halt in a nonfinal state.
For example, consider:
Turing machines are more than just language accepters. They provide a simple abstract model for computers in general. Computers transform data. Hence, Turing machines are transducers (as we defined them in Chapter 1). For a computation, the
input consists of all the nonblank symbols on the tape initially
output consists of is whatever is on the tape when the machine halts in a final state
Thus, we can view a Turing machine transducer as an implementation of a function defined by
provided that
,
for some final state .
Linz Definition 9.4 (Turing Computable): A function with domain is said to be Turing-computable, or just computable, if there exists some Turing machine such that
, ,
for all .
Note: A transducer Turing machine must start on the leftmost symbol of the input and stop on the leftmost symbol of the output.
Compute for positive integers and .
We use unary notation to represent the positive integers, i.e., a positive integer is represented by a sequence of 1’s whose length is equal to the value of the integer. For example:
The computation is
where separates the two numbers at initiation and after the result at termination.
Key idea: Move the separating to the right end.
To achieve this, we construct with
The sequence of instantaneous descriptions for adding 111 to 11 is shown below.
Construct a Turing machine that copies strings of ’s. More precisely, find a machine that performs the computation
,
for any .
To solve the problem, we implement the following procedure:
A Turing machine transition function for this procedure is as follows:
where is the only final state.
Linz Figure 9.7 shows a transition graph for this Turing machine.
This is not easy to follow, so let us trace the program with the string 11. The computation performed is as shown below.
Suppose and are positive integers represented in unary notation.
Construct a Turing machine that halts in a final state if and in a nonfinal state if .
That is, the machine must perform the computation:
, if
, if
We can adapt the approach from Linz Example 9.7. Instead of matching ’s and ’s, we match each 1 on the left of the dividing 0 with the 1 on the right. At the end of the matching, we will have on the tape either
or
,
depending on whether or .
A transition graph for machine is shown below.
How can we compose simpler operations on Turing machines to form more complex operations?
Techniques discussed in this section include use of:
Top-down stepwise refinement, i.e., starting with a high-level description and refining it incrementally until we obtain a description in the actual language
Block diagrams or pseudocode to state high-level descriptions
In the block diagram technique, we define high-level computations in boxes without internal details on how computation is done. The details are filled in on a subsequent refinement.
To explore the use of block diagrams in the design of complex computations, consider Linz Example 9.12, which builds on Linz Examples 9.9 and 9.11 (above).
Design a Turing machine that computes the following function:
, if ,
, if .
For simplicity, we assume and are positive integers in unary representation and the value zero is represented by 0, with the rest of the tape blank.
Linz Figure 9.8 shows a high-level block diagram of this computation. This computation consists of a network of three simpler machines:
We use such high-level diagrams in subsequent discussions of large computations. How can we justify that practice?
We can implement:
the Comparer program as suggested in Linz Example 9.11, using a Turing machine having states indexed with
the Adder program as suggested in Linz Example 9.9, with states indexed with
the Eraser program by constructing a Turing machine having states indexed with
Comparer carries out the computations
, if ,
and
, if .
If and are the initial states of computations and , respectively, then starts either or .
Adder carries out the computation
.
And, Eraser carries out the computation
.
The outer diagram in Linz Figure 9.8 thus represents a single Turing machine that combines the actions of machines , , and as shown.
In the pseudocode technique, we outline a computation using high-level descriptive phrases understandable to people. We refine and translate it to lower-level implementations later.
A simple kind of pseudocode is the macroinstruction. A macroinstruction is a single statement shorthand for a sequence of lower-level statements.
We first define the macroinstructions in terms of the lower-level language. Then we compose macroinstructions into a larger program, assuming the relevant substitutions will be done.
For this example, consider the macroinstruction
if then else .
This means:
If the Turing machine reads an , then it, regardless of its current state, transitions into state without changing the tape content or moving the read-write head.
If the symbol read is not an , then it transitions into state without changing anything.
We can implement this macroinstruction with several steps of a Turing machine:
for all
for all
for all and all
for all
States and just back up Turing machine tape position one place.
Macroinstructions are expanded at each occurrence.
While each occurrence of a macroinstruction is expanded into actual code, a subprogram is a single piece of code that is invoked repeatedly.
As in higher-level language programs, we must be able to call a subprogram and then, after execution, return to the calling point and resume execution without any unwanted effects.
How can we do this with Turing machines?
We must be able to:
preserve information about the calling program’s configuration (state, read-write head position, tape contents), so that it can be restored on return from the subprogram
pass information from the calling program to the called subprogram and vice versa
We can do this by partitioning the tape into several regions. Linz Figure 9.9 illustrates this technique for a program (a Turing machine) that calls a subprogram (another Turing machine).
Note: This is similar to what happens in an actual computer for a subprogram (function, procedure) call. The region is normally a segment pushed onto the program’s runtime stack or dynamically allocated from the heap memory.
Design a Turing machine that multiplies and , positive integers represented in unary notation.
Assume the initial and final tape configurations are as shown in Linz Figure 9.10.
We can multiply by by adding to itself times as described in the algorithm below.
Although the above description of the pseudocode approach is imprecise, the idea is sufficiently simple that it is clear we can implement it.
We have not proved that the block diagram, macroinstruction, or subprogram approaches will work in all cases. But the discussion in this section shows that it is plausible to use Turing machines to express complex computations.
The Turing thesis is an hypothesis that any computation that can be carried out by mechanical means can be performed by some Turing machine.
This is a broad assertion. It is not something we can prove!
The Turing thesis is actually a definition of mechanical computation: a computation is mechanical if and only if it can be performed by some Turing machine.
Some arguments for accepting the Turing thesis as the definition of mechanical computation include:
Anything that can be computed by any existing digital computer can also be computed by a Turing machine.
There are no known problems that are solvable by what we intuitively consider an algorithm for which a Turing machine program cannot be written.
No alternative model for mechanical computation is more powerful than the Turing machine model.
The Turing thesis is to computing science as, for example, classical Newtonian mechanics is to physics. Newton’s “laws” of motion cannot be proved, but they could possibly be invalidated by observation. The “laws” are plausible models that have enabled humans to explain much of the physical world for several centuries.
Similarly, we accept the Turing thesis as a basic “law” of computing science. The conclusions we draw from it agree with what we know about real computers.
The Turing thesis enables us to formalize the concept of algorithm.
Linz Definition 9.5 (Algorithm): An algorithm for a function is a Turing machine , which given as input any on its tape, eventually halts with the correct answer on its tape. Specifically, we can require that
,
for all .
To prove that “there exists an algorithm”, we can construct a Turing machine that computes the result.
However, this is difficult in practice for such a low-level machine.
An alternative is, first, to appeal to the Turing thesis, arguing that anything that we can compute with a digital computer we can compute with a Turing machine. Thus a program in suitable high-level language or precise pseudocode can compute the result. If unsure, then we can validate this by actually implementing the computation on a computer.
Note: A higher-level language is Turing-complete if it can express any algorithm that can be expressed with a Turing machine. If we can write a Turing machine simulator in that language, we consider the language Turing complete.