CSE596 Problem Set 5 Answer Key Fall 2015 (A) A program verification system . . . gives us a program Q—itself total—such that if Q accepts a program P , then P is total. The limitation—but which also enables Q to be real code—is that we don’t always get the converse: Q is allowed to reject some programs that really are total. In general terms, Q is allowed to give false negatives but not false positives. . . . [A]lso the following program P is real code—not “paradoxical”: bool P(string z) { if (z fails to compile to a valid program M) { reject; } //else we have M if (Q rejects z) { reject; } //now we know M is total if (M accepts z) { reject; } else { accept; } } This program P is total—crucially because Q is total and Q never gives a false positive. We humans can see that. The question is, can our verification system “see” it? Take p to be the code of P and q to be the code of Q: so p compiles to P and q compiles to Q. (If you like the e(·) function, p = e(P ) and q = e(Q).) (a) Show that Q cannot accept p. (b) What actually happens when P is run on input p? (c) If Q accepts q, does that cause a contradiction when P is run on q? Answer: (a) Two answers were fine: (i) If Q accepts p and we suppose the third line is executed to completion (based on our saying P is total) when P is run on input p, then we get the diagonal contradiction from P : P accepts p ⇐⇒ P rejects p. (ii) What actually would happen if Q accepted p is that, since “M ” is literally P itself in the third line, P when run on p would invoke itself on input p in that line. This causes an infinite recursion, which formally means not halting (in practice it would “segfault” with a stack overflow). But that in turn contradicts P being total and the given assumption that Q gives no false positives—since now “Q(p) accepts” is a false positive. Either way, something’s gotta give—you can have the assumptions of the problem but not with Q accepting p. (b) Since Q, being total, must reject p by (a), P (p) rejects in that same second line. (c) If Q accepts q, then P on input q runs Q(q) and gives the opposite answer. That’s fine because it’s P (q) versus Q(q); it just means that P and Q cannot accept the same language. In jargon terms, it means “P is diagonal for Q.” Actually it’s the language L(P ) of P that is diagonal for Q, which takes us into the for-credit problem. (1) Show that Q cannot accept the code of any program M such that L(M ) = L(P ). (12 pts.) Answer: Suppose there were such a program M —since we are thinking of a specific one now we can more helpfully call it P 0 since it is “like P .” Take p0 to stand for the code of P 0 —then we are supposing that Q accepts p0 and L(P 0 ) = L(P ). We produce a contradiction as follows: Run P on p0 . Then in the first step it compiles p0 to get a working copy of P 0 and in the second step Q accepts p0 —which means P 0 is total by the assumption about Q. So the third step runs to completion. We get: P accepts p0 ⇐⇒ ⇐⇒ P 0 rejects p0 P rejects p’ (by third step) (by L(P 0 ) = L(P )). This is our old friend (or “fiend”) the diagonal contradiction, which completes the proof. Remarks: Notice that the contradiction comes about because of the assumption that languages are equal—so p0 ∈ L(P 0 ) ⇐⇒ p0 ∈ L(P ), so P 0 accepts p0 ⇐⇒ P accepts p0 . It does not rely on any idea that P 0 must work like P does—assuming the program P 0 must work like P is another issue of “intension vs. extension.” It comes down to the particular language—call it just L now—that P accepts. The jargon term here is that L is diagonal for Q. The language not just the program is diagonal. The upshot is that Q not only cannot accept the code of P , it cannot accept the code of any program that recognizes L. This is so even though L is a decidable language—since there really are cases where programs like Q exist and are practically relevant, and then P really exists as above and is total. The ulterior motives for this topic are: • Any formal system F of logic that can analyze computations (technically the bar is very low: F only needs to express and prove some basic identities of arithmetic and induction on N) can give you a program verification system Q without false positives. But then the statements “P is total” and “some total program P 0 accepts L” are true but unprovable in F . This is a neat way to appreciate Gòˆdel’s Theorem that for every consistent formal system with implementable proofs there are true statements that the system cannot prove. This is not on the course syllabus but is important to the intellectual context of the course. • When we hit Section 5.5 of the text we will see a diagonalization technique where the fact that it applies to all programs for a diagonal language is essential. There will be an extra component that will allow us to “morph” the diagonal language so that it fits inside a slightly-higher complexity class. If you get this first, that—which is central to the second-half syllabus—will be easier to follow. (2) Consider the triad of problems you get from the program behavior that a single-tape Turing machine M overwrites a non-blank character by the blank B. (A basic fact you need to know is that Turing machines can always be programmed to treat a char like ‘%’ as a “virtual blank” and avoid writing the actual blank B, unless. . . ) Stated as decision problems, their questions are: 1. Given M and x, does M (x) ever overwrite a non-blank char by B? 2. Given M , is there some x such that M (x) does that behavior? 3. Given M , does M (x) do that behavior for all x? State the languages L1 , L2 , L3 of these problems using set notation, and then prove that all three languages are undecidable. (39 pts. total, for 51 on the set) Answer: To help visualize, I will use M 0 rather than M for the “dummy variable” in these sets. (For L1 I could also “dummy” x0 instead of x, but since it’s the same x the visual help is not so important.) There is nothing wrong with using “M ” for them though. L1 = {hM 0 , xi : M 0 (x) overwrites a non-blank char by B} L2 = {e(M 0 ) : for some x, M 0 (x) overwrites a non-blank char by B} L3 = {e(M 0 ) : for all x, M 0 (x) overwrites a non-blank char by B} Now to prove that these languages are undecidable, it suffices to reduce the Acceptance Problem (or essentially equivalently the Halting Problem) to each of them. Given an instance hM, xi of the APT M problem, we kill all three birds with one stone by defining a particular Turing machine M 0 that operates as follows: M 0 : On any input w, M 0 (w) first (writes down x on another tape or next to w on the input tape, whatever—you don’t need to say this) starts up a simulation of M on input x. Importantly, the simulation of M (x) operates without ever writing a literal blank—it can use a special char ‘%’ (that is not used by any TM in the formalized instances of APT M ) as a “virtual blank.” If and when M accepts x, M 0 (w) then proudly writes a literal blank B over a non-blank char. It is easy to stitch a given M and x into the code for M 0 , so the functions f1 (M, x) = hM, xi and f2 (M, x) = e(M 0 ) are computable. We get this analysis: hM, xi ∈ APT M ⇐⇒ ⇐⇒ ⇐⇒ ⇐⇒ ⇐⇒ ⇐⇒ ⇐⇒ M accepts x M 0 (x) overwrites a non-blank char by B hM 0 , xi ∈ L1 f1 (M, x) ∈ L1 (so APT M ≤m L1 ) 0 (∃w)M (w) overwrites a non-blank char by B e(M 0 ) ∈ L2 f2 (M, x) ∈ L2 (so APT M ≤m L2 ). Finally, by the logic of the “all-or-nothing-switch,” the same function f2 also many-one reduces APT M to L3 , because hM, xi ∈ APT M ⇐⇒ ⇐⇒ ⇐⇒ ⇐⇒ M accepts x (∃w)M 0 (w) overwrites a non-blank char by B (∀w)M 0 (w) overwrites a non-blank char by B f2 (M, x) ∈ L3 (so APT M ≤m L3 ).