Answers are in blue.
Three questions concerning Bloop, defined in HW#2 and the resulting questions and answers:
It's pretty easy to prove one direction, because we have a nice inductive
definition of the primitive recursive functions. We write Bloop programs for
each initial function, for example pi32 is
define pi32(x,y,z) return y;
. Then closure of Bloop programs
under composition is obvious from our definition -- the new program just
makes function calls on the programs for the pieces. For closure under
primitive recursion we must convert the recursion into a bounded loop.
For example if f(n,x,y) is defined by f(0,x,y) = g(x,y) and
f(n+1,x,y) = h(f(n,x,y),n,x,y), then we may write:
define f(n,x,y) {
w = g(x,y);
i = 0;
loop n times {
w = h(w,i,x,y);
i++;}
return w;}
For the other direction, the real difficulty is to get the right inductive hypothesis for the proof. How about "for every block B of Bloop code, for every variable v in B, the value of v after B is executed is a primitive recursive function of its inputs", where the inputs to a block are the variable values defined when it starts executing. Now we have to show that this is true
If B is a single statement it is either an assignment of an expression to a variable (in which case the new values are sums of products of old values and are p.r. functions of the old ones) or a non-recursive function call, which fits under our inductive hypothesis.
If B is C followed by D then the behavior of B is a composition of the behaviors of C and D, in a way that I would write down if I had more time but should be pretty obvious.
If B is "loop x times {C}", then we use primitive recursion to define B's behavior, where for each variable a fa(n,...) is the value of a after the loop has been executed n times. This can be defined by primitive recursion, where ga is the identity function (since the loop is a no-op if executed zero times, and ha is the p.r. function describing the behavior of C (available by the inductive hypothesis).
Since all Bloop programs are built up by these operations, they all are described by p.r. functions. A correct proof of this half of the problem could differ from mine by quite a bit, depending on how you decide to define Bloop. (I admit I was pretty vague about it, with the intention of letting you grapple with the difficulty of putting a mathematical model to a more real-looking construct like Bloop.)
Well, after (a) we can prove that every p.r. function is total recursive.
We explicitly design always-halting TM's that compute each of the basic
functions.
Then we show closure under composition: if for example
f(y,z) = h(g1(y,z),g2(y,z),g3(y,z)), and
there are already known to be always-halting TM's for f and the
gi's, then all we need to argue is that there is an always-halting
TM computing h. It starts with y and z on its tape, computes
g1(y,z), g2(y,z), and g3(y,z) and puts those
on another tape, then runs the h-machine as if that second tape were the
input.
Finally we show closure under primitive recursion. Here we can actually
implement recursion in the usual way, leaving an activation record behind
(on a tape reserved for our method stack) for each recursive call. We know
that the recursion depth will be only n (the first argument to f), that each
invocation of h will call f with first argument one smaller and halt once
that call returns, and finally that the last call to g will halt. So the whole
TM will halt with the correct answer.
Let's go with BLUEDIAG. We'll say that if n happens to code a Bloop program with one argument, that the function computed by this program is fn. If n doesn't code such a program, define fn to be the always-zero function. Then, as suggested, we define BLUEDIAG(n) to be fn(n)+1.
It's clear that BLUEDIAG cannot be a Bloop-computable function, because if it had a program, that program would be coded by some number n, and then BLUEDIAG(n) would be fn(n)+1 instead of fn(n). So these two functions would not agree on all inputs and thus would not be the same.
Why is BLUEDIAG a total recursive function? We need to argue that there is an always-halting Turing machine that can compute BLUEDIAG(n) on input n. This machine must interpret n as the code for a Bloop program, and if it can it must see whether this program has one argument. If it is not a valid one-argument program, our machine outputs 1 and halts. If it is valid, our machine must simulate it on input n, add one to the answer, and halt.
The simulation is similar to the simulation of a Floop program by a Turing machine in HW#2, Question 3(e). We set up a tape area (or even a whole tape) for each variable in the program, and then run through the program seeing which variable to update at each step and how to calculate the new value. Because the Bloop program is guaranteed to halt, so is our Turing machine.
Make an array of boolean flags, one for each nonterminal symbol of G. Each
of these flags is originally false, and will be set true if and only if
we establish that its nonterminal can generate a string of terminals.
We make a succession of passes over the rules of G. In each pass we look
for a rule "A &rightarrow w" where all the letters in w are either terminals
or flagged true. Each time we find such a rule we set A's flag to true.
If we set the start symbol's flag to true we halt and return "true", and if
we go through an entire pass without changing a flag we halt and return
"false".
If there are n nonterminals
in G, we will make no more than n passes, because
in each pass we either increase the number of true flags by at least one or
we halt and this number can't exceed n. Each pass takes time proportional
to the number of rules times the length of the longest rule, hence clearly
polynomial time.
These are the only two possibilities because if A includes a machine, it also
by hypothesis includes all other machines that have the same language. So
if any machine with empty language is in A, they are all in A.
In this case we begin by choosing a machine M' that is not in A
(which we can do
because A does not contain all machines). We then reduce K-bar (the complement
of K) to A as follows. On input n, design the new machine Mf(n)
so that it saves its input x, runs Mn on n, and then (if
Mn accepts n) runs M' on x. If Mn accepts n, then the
language of Mf(n) is the same as the language of M' and thus
f(n) is in A. If Mn does not accept n, M' has an empty language
and thus f(n) is not in A. So f reduces K-bar to A, and since K-bar is not
recursive neither is A. For that matter, in this case since we know that
K-bar is not even r.e., we can conclude that A is not r.e. either.
(x1 or x2) and (x3 or x4) and ... and (x2n-1 or x2n)
has a disjunctive normal form with 2n different terms.
We get the formula by applying the distributive law to the given formula and collecting terms. The DNF formula is the OR of the 2n terms that are obtained by choosing one variable from each pair {x2i-1, x2i} in each possible way. We could prove this by induction, or just appeal to our knowledge of algebra and the fact that the same distributive law works in this context.
The length of this expression is O(n) terms, actually O(n log n) bits since the subscripts are O(log n) bits each. The equivalent DNF expression is over 2n bits long since it contains that many terms, and 2n is larger than any polynomial in the input size. Hence no polynomial-time algorithm would have time to write the desired output, even if it were able to figure out what to write.
An expression is satisfiable if there is some setting of the variables
making it true. Hence a DNF expression is satisfiable if there is any
setting making any of its clauses true. A clause is an AND of literals,
where literals are variables or negated variables. A clause cannot be
made true if it contains two literals that are negations of each other,
but otherwise it can be made true by setting each variable to agree with
the form it takes in the clause, if any.
The poly-time algorithm for satisfiability thus examines each clause
in turn to see whether two of its literals are negations of each other.
If it finds a clause where this does not happen, it returns "true", and if
it finds eventually that it does happen in each clause, it returns "false".
The exact time will depend on the format for presenting the formula, but
the number of comparisons between literals is mo more than the sum of the
squares of the sizes of the clauses, which is no more than n2
so this is pretty clearly polynomial in the length of the formula.
My hint can be carried through but works better if it is adjusted slightly.
Consider the n pairs of variables
{x2i-1, x2i} for i from 1 through n. I claim that
Suppose clause C is true in some setting s, but C contains no literal
involving either variable x2i-1 or x2i. Make a new
setting from s by changing these two variables to false if they weren't
false already. Now C is still true, since no variable occurring in it was
changed. But 4(a) is false because "x2i-1 OR x2i
is false.
Now for the other claim. My 2n settings are those that have
exactly one variable true in each pair. Let C be a clause with at least
one literal from each pair. In each pair, this literal forces the choice of
which variable is to be true, if only one is true. Since the choice in each
pair is forced, the choice of setting is forced and all other settings in this
group make C false.
From these two statements it follows that any correct DNF has at least
2n clauses, because it needs at least one for each of these special
settings. So it remains to justify the two claims.
Last modified 26 March 2003