Title

ECS 120 Theory of Computation
Undecidability
David Doty (adapted from slides authored by Julian Panetta)
University of California, Davis

Undecidability

  • We now study the absolute limits of what can be computed by algorithms.
  • Whereas \(\NP\)-complete problems are not (known to be) solvable by efficient algorithms,
    certain problems are not solvable by any algorithm no longer how much time is allowed.
  • The first example of such a problem is the halting problem: \[ \probHALT = \setbuild{\encoding{M, w}}{M \text{ is a Turing machine that halts on } w} \]

Recall the terminology for TM computation:

A language \(A\) is called Turing-recognizable (or Turing-acceptable, recursively enumerable, or
computably enumerable) if some Turing machine \(M\) recognizes it (\(L(M) = A\)).

A language \(A\) is called Turing-decidable if there is a total Turing machine \(M\) (a decider) such that \(L(M) = A\). In other words \(M\) accepts all \(w \in A\) and rejects all \(w \notin A\).

Recognizing \(\probHALT\)

  • A recognizer is easy to write for \(\probHALT\):
    • Given \(\encoding{M, w}\), run \(M\) on \(w\).
    • If \(M\) halts, accept.
    • Otherwise, we can simply run forever.
def halt_recognizer(M, w):
  """
  M: a Python function
    (stand-in for a Turing machine)
  w: an input string
  """
  M(w)
  return True
def M1(w):
  count = 0
  for c in w:
      if c == 'a':
          count += 1
  return count > 5

w = 'abcabcaaabcaa'
halt_recognizer(M1, w) # returns True
def M2(w):
  i = 0
  while i < len(w):
      if w[i] != 'c':
          i += 1
  return w

w = 'abcabcaaabcaa'
halt_recognizer(M2, w) # never returns...

Theorem (Turing): \(\probHALT\) is not decidable.

Once we prove this, we can use Turing reductions to show that many other problems are also undecidable.

Turing reductions

  • To prove a problem \(A\) is undecidable, we can reduce from \(\probHALT\).
    • We show how \(\probHALT\) can be decided by a decider for \(A\).
    • Since \(\probHALT\) is not decidable, the decider for \(A\) cannot exist.
  • The reductions we use for decidability are more general than the โ€œmapping reductionsโ€ we used to prove \(\NP\)-hardness.
  • The reduction we used from \(\NP\)-hard problem \(A\) to prove \(B\) is \(\NP\)-hard:
    • Used input conversion function \(f\) that runs in polynomial time.
    • Returned \(M_B(f(x))\) (ran \(M_B\) exactly once and returned its output unmodified).
  • Turing reductions relax these requirements, allowing:
    • arbitrary time complexity for \(f\),
    • multiple calls to \(M_B\) on potentially different inputs, and
    • arbitrary (but finite) computation on the results (for instance negating a Boolean result).

More undecidable problems: No-input halting

Simple example of a Turing reduction:
showing that the no-input Halting problem is undecidable

\[ \probHALT_\emptystring = \setbuild{\encoding{M}}{M \text{ is a Turing machine and } M(\emptystring)} \]

\(\probHALT_\emptystring\) is obviously reducible to \(\probHALT\) since it is just a special case!
We could decide if \(\encoding{M} \in \probHALT_\emptystring\) by checking if \(\encoding{M, \emptystring} \in \probHALT\).

But \(\probHALT_\emptystring\) could easierโ€ฆ

  • Weโ€™ll show \(\probHALT\) is reducible to \(\probHALT_\emptystring\).
    • If we had an algorithm to decide \(\probHALT_\emptystring\),
      we could use it to decide \(\probHALT\).
    • Since \(\probHALT\) is undecidable, this algorithm cannot exist!

More undecidable problems: No-input halting

Simple example of a Turing reduction:
showing that the no-input Halting problem is undecidable

\[ \probHALT_\emptystring = \setbuild{\encoding{M}}{M \text{ is a Turing machine and } M(\emptystring) halts} \]

Theorem: \(\probHALT_\emptystring\) is undecidable.

Proof:

  • Suppose that \(\probHALT_\emptystring\) is decidable by an algorithm:

    from typing import Callable
    def H_๐œ€(M: Callable[[str], bool]) -> bool: raise NotImplementedError()
  • We can use this to decide \(\probHALT\) as follows:

    def H(M: Callable[[str], bool], w: str) -> bool:
        # Create a new algorithm T_Mw that ignores its input
        # and has the original input w "hardcoded".
        def T_Mw(x: str) -> bool:  # This is algorithm is never actually run!!!
            M(w)                   # Executing these lines merely *defines* T_Mw.
        return H_๐œ€(T_Mw)           # ... but instead is *analyzed* by H_๐œ€.
  • If M(w) halts, then:
    • T_Mw halts on every input (including \(\emptystring\)).
    • So H_๐œ€(T_Mw)returns True.
  • If M(w) loops (doesnโ€™t halt), then:
    • T_Mw loops on every input (including \(\emptystring\)).
    • So H_๐œ€(T_Mw) returns False.
  • Therefore H decides \(\probHALT\), a contradiction.

Undecidability of algorithm โ€œbehaviorโ€: General recipe

  • The previous argument follows a pattern we will use to show that many other questions we might ask about an algorithmโ€™s eventual behavior are undecidable.

  • We prove that an algorithm \(A\) deciding that other question cannot exist by showing it can be used to solve the Halting problem.

  • We will use it to solve \(\probHALTe\), which is slightly more convenient than \(\probHALT\). (one fewer variable)

  • The steps to construct an algorithm for \(\probHALTe\) are:

    1. Take as input an arbitrary Turing machine/Python function \(M\); we want to know if \(M(\varepsilon)\) halts.
    2. Construct a new Turing machine \(T_M\) that:
      1. Runs \(M(\emptystring)\).
      2. Has opposite โ€œbehaviorโ€ (that \(A\) is looking for) if \(M(\emptystring)\) halts than if it does not halt.
    3. Pass \(T_M\) to algorithm \(A\); use the result to decide if \(M(\emptystring)\) halts.
  • We did (essentially) this when reducing \(\probHALT\) to \(\probHALTe\) by:

    1. Taking as input \((M,w)\).
    2. Constructing \(T_{M,w}\) that ignores its input and runs \(M(w)\).
    3. Passing \(T_{M,w}\) to hypothetical decider for \(\probHALTe\), noting \(T_{M,w}(\emptystring)\) halts if and only if \(M(w)\) halts, tying the โ€œbehaviorโ€ of \(\probHALT\) (\(M(w)\) halting or not) to the โ€œbehaviorโ€ of \(\probHALTe\) (\(T_{M,w}(\emptystring)\) halting or not).

More undecidable problems: Acceptance problem

Letโ€™s apply this recipe to the following problem:

\[ \probAccepts = \setbuild{\encoding{M, w}}{M \text{ is a Turing machine that accepts } w} \]

Theorem: \(\probAccepts\) is undecidable.

Proof:

  • Suppose that \(\probAccepts\) is decidable by alg. A:

    def A(M, w): raise NotImplementedError()
  • We can use A to decide \(\probHALTe\) as follows:

    def H_๐œ€(M):
        # Create a new algorithm T_M that accepts any input
        # if and only if M halts on the empty string.
        def T_M(w):          # Input is ignored!
            M('')
            return True
        return A(T_M, '011') # Analyze T_M using hypothetical decider A
  • If M('') halts, then:
    • T_M accepts all inputs (including \(011\)).
    • So A(T_M, w) and H_๐œ€ return True.
  • If M('') loops (does not halt), then:
    • T_M never reaches the return, so accepts no inputs (including \(011\)).
    • So A(T_M, w) and H_๐œ€ return False.
  • Formally: \[ \begin{aligned} M(\emptystring) \text{ halts } &\iff T_M \text{ accepts } \string{011} \\ &\iff A \text{ accepts } \encoding{T_M, 011} \\ &\iff H_๐œ€ \text{ accepts } \encoding{M} \end{aligned} \]
  • Therefore H_๐œ€ decides \(\probHALTe\),
    a contradiction.

More undecidable problems: Empty language problem

\[ \probEmpty = \setbuild{\encoding{M}}{M \text{ is a Turing machine and } L(M) = \emptyset} \]

In other words: \(M\) accepts no inputs.

Theorem: \(\probEmpty\) is undecidable.

Proof:

  • Suppose that \(\probEmpty\) is decidable by algorithm E:

    def E(M): raise NotImplementedError()
  • We can use E to decide \(\probHALTe\) as follows:

    def H_๐œ€(M):
        # Create a new algorithm that accepts a string
        # if and only if M halts on the empty string.
        def T_M(w):
            M('')
            return True
        return not E(T_M) # Analyze T_M using hypothetical decider E
  • If M('') halts, then:
    • T_M accepts all inputs, so \(L(T_M) \ne \emptyset\).
    • So E(T_M) returns False, so
      H_๐œ€ returns True.
  • If M('') loops, then:
    • T_M never reaches the return, so accepts no inputs, so \(L(T_M) = \emptyset\).
    • So E(T_M) returns True, so
      H_๐œ€ returns False.
  • Therefore H_๐œ€ decides \(\probHALTe\),
    a contradiction.

Could we instead make T_M return False and H_๐œ€ return E(T_M)?

More undecidable problems: Rejecting problem

\[ \probRejects = \setbuild{\encoding{M}}{M \text{ is a Turing machine that rejects at least one input}} \]

Theorem: \(\probRejects\) is undecidable.

Proof:

  • Suppose that \(\probRejects\) is decidable by algorithm R:

    def R(M): raise NotImplementedError()
  • We can use R to decide \(\probHALTe\) as follows:

    def H_๐œ€(M):
        # Create a new algorithm that rejects a string
        # if and only if M halts on the empty string.
        def T_M(w):
            M('')
            return False
        return R(T_M) # Analyze T_M using hypothetical decider R
  • If M('') halts, then:
    • T_M rejects all inputs.
    • So R(T_M) returns True, so
      H_๐œ€ returns True.
  • If M('') does not halt, then:
    • T_M never reaches the return, so rejects no inputs.
    • So R(T_M) returns False, so
      H_๐œ€ returns False.
  • Therefore H_๐œ€ decides \(\probHALTe\),
    a contradiction.

How to spot undecidability at a glance

  • When faced with a new problem, we can always follow the recipe to show itโ€™s undecidable.
  • But often we can spot at a glance that the problem must be undecidable.
  • Questions about โ€œeventualโ€ algorithm โ€œbehaviorโ€ are undecidable.
  • Example: given program \(P\) and input \(x\):
    • Does \(P\) accept x?
    • Does \(P\) reject x?
    • Does \(P\) loop on x?
    • Does \(P(x)\) ever call subroutine \(f\)?
  • Example questions about general program behavior:
    • Does \(P\) accept at least one input?
    • Does \(P\) accept all inputs?
    • Is \(P\) total?
    • Is there an input \(x\) such that \(P(x)\) returns 42?
    • Does some input cause \(P\) to call subroutine \(f\)?
    • Do \(P\) and \(Q\) decide the same language?
    • Is \(P\) a polynomial-time machine?

Decidable problems about programs

  • Not all questions we can ask about a program are undecidable though!
  • Questions about syntax are decidable:
    • Does \(P\) have 100 lines of code?
    • Does \(P\) have 2 and 3 in its input alphabet?
    • Does \(P\) halt immediately because its first line is an immediate return without a subroutine call?
    • Does \(P\) have a subroutine named f (whether or not it is ever called)?
  • โ€œBoundedโ€ questions about behavior are decidable:
    • Does \(P(x)\) halt within 10 steps?
    • Does \(P(x)\) halt within \(|x|^3\) steps?
    • Given \(P\) and \(n\), does \(P\) halt within \(n^3\) steps for all inputs of length \(n\)? (โ€œonlyโ€ takes time \(O(n^3 \cdot 2^n)\))
    • Does \(P(x)\) call subroutine \(f\) in the fifth step?
  • Careful: if the question is bounded but about infinitely many inputs, it may still be undecidable!
    • Given \(P\), is there an input \(x\) such that \(P(x)\) halts within \(|x|\) steps? (looks superficially like an \(\NP\) question, but \(x\) could be huge compared to \(|\encoding{P}|\))
    • Given \(P\), does \(P\) halt within \(|x|\) steps for all inputs \(x\)?

Some examples

Which of the following problems, about program \(P\) (and possibly input \(x\)) is decidable?