20. Uncomputable Functions

The Power of Encoding

The basic object that we consider in the study of computability is a function \(f \colon \sigma^* \to \sigma^*\) which maps strings of symbols from some finite alphabet \(\sigma\) to other strings over the same alphabet. We already saw that even the simple binary alphabet \(\sigma = \{0,1\}\) suffices to let us encode natural numbers (using their natural binary representation) as strings so that this model lets us study the computability of functions on natural numbers.

We can also represent many, many other types of mathematical objects with strings.

\[\begin{bmatrix} 2 & 0 \\ -1 & 5 \\ 3 & 4 \end{bmatrix} \qquad \Rightarrow \qquad (2,0; -1,5; 3, 4).\] \[\begin{matrix} x & y & x \wedge y \\ \hline 1 & 1 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 0 \end{matrix} \qquad \Rightarrow \qquad (1, 0, 0, 0)\]

But the most interesting mathematical object to encode as a string in the study of computability is none of the above examples: it is Turing machines.

Big Idea. We can encode many different mathematical objects as strings, even Turing machines.

To see why Turing machines can be encoded as strings, we can go back to their definition in the last lecture. We can specify a Turing machine by fixing the number of internal states it contains, the number of symbols in the tape alphabet, and the transition function. And combining the observations above, we see that we can encode these three elements in a string.

Functions on Turing Machines

Once we know we can encode Turing machines as strings, the next natural question to ask is: what kind of functions whose inputs contain Turing machines might we want to consider? There are tons of natural problems about Turing machines that we can consider:

As it turns out, only one of the above functions is computable. But there is one surprising thing that we can do with Turing machines: we can construct one Turing machine that can simulate all the other ones.

Theorem 1. There is a universal Turing machine \(U\) that for each input \(\left< M \right>, x\) simulates the Turing machine \(M\) on input \(x\) in the following sense:

This theorem has far-reaching consequences, but our interest for now is to prove that a specific function is uncomputable. And the best way to do that is to start with a function that is different from all the ones listed above. Consider the following function that considers what a Turing machine outputs when it is given its own description as input:

\[\mathsf{SelfReject}(\left< M \right>) = \begin{cases} 1 & \mbox{if } M \mbox{ outputs 0 on input } \left<M\right> \\ 0 & \mbox{otherwise.} \end{cases}\]

(This function is analogous to the set of all sets that don’t contain themselvesin Russell’s paradox.)

Theorem 2. The \(\mathsf{SelfReject}\) function is uncomputable.

Proof. Assume for the sake of contradiction that there is a Turing machine \(D\) that computes \(\mathsf{SelfReject}\). Consider \(D\)’s output when we run it with input \(\left< D \right>\).

In both cases, we find that \(D\) does not output \(\mathsf{SelfReject}(\left<D\right>)\) on input \(\left< D \right>\), contradicting our assumption that it computes this function. Therefore, there is no Turing machine that computes \(\mathsf{SelfReject}\).

The proof of the uncomputability of \(\mathsf{SelfReject}\) is an example of a diagonalization argument.

Reduction

Once we have one uncomputable function, we can use it to prove that many other functions are uncomputable using reductions. Here’s one example of a reduction argument. Consider the function

\[\mathsf{Halting}(\left< M \right>, x) = \begin{cases} 1 & \mbox{if } M \mbox{ halts on input } x \\ 0 & \mbox{otherwise.} \end{cases}\]

Theorem 3. The \(\mathsf{Halting}\) function is uncomputable.

Proof. Assume for contradiction that there is a Turing machine \(T\) that computes the \(\mathsf{Halting}\) function. Let \(C\) be the Turing machine that takes \(\left< M \right>\) as input and:

Let us now examine the function that \(C\) computes. First, we see that \(C\) always halts, because \(T\) does (by assumption) and because \(C\) only simulates \(M\) when it is guaranteed that \(M\) will halt. When \(M\) halts on input \(\left< M \right>\) and outputs 0, then \(C\) outputs 1. Otherwise, if \(M\) halts on input \(\left< M \right>\) with any other output string or if it does not halt at all on this input, then \(C\) outputs 0. In other words, \(C\) computes the \(\mathsf{SelfReject}\) function. But this contradicts Theorem 2 that no such Turing machine exists, so our assumption that there exists a Turing machine which computes \(\mathsf{Halting}\) is false.