han@fornax.UUCP (Jiawei Han) (05/24/90)
It poses a quite interesting question. Let me try it also. The problem is whether it is worthwhile to compile a linear mutual recursion and in what way we should compile it. We examine the same example. The original linear mutual recursion is female_ancestor(X, X) :- female(X). female_ancestor(X, Y) :- mother(X, Z), female_ancestor(Z, Y). female_ancestor(X, Y) :- mother(X, Z), male_ancestor(Z, Y). male_ancestor(X, X) :- male(X). male_ancestor(X, Y) :- father(X, Z), female_ancestor(Z, Y). male_ancestor(X, Y) :- father(X, Z), male_ancestor(Z, Y). Method 0: Do not transform the recursion. Use magic sets method to evaluate the recursion directly. The magic sets method works on it with no problem. Method 1: Transform a set of mutually recursive predicates into the same predicate by introducing an extra argument and assign a distinct constant to it for each mutually recursive predicate in the recursion. anc(c1, X, X) :- female(X). anc(c1, X, Y) :- mother(X, Z), anc(c1, Z, Y). anc(c1, X, Y) :- mother(X, Z), anc(c2, Z, Y). anc(c2, X, X) :- male(X). anc(c2, X, Y) :- father(X, Z), anc(c1, Z, Y). anc(c2, X, Y) :- father(X, Z), anc(c2, Z, Y). The translated program is a multiple linear recursion (having multiple linear recursive rules). However, the translated rule is not a chain rule any more. We probably still want to evaluate it using the magic sets method. It works essentially in the same way with same level of efficiency as Method 0. Method 2: Compile it based on the class of the linear mutual recursion. We can simple present it in a short form as below (where we drop variables in the predicates since the recursive rules are chain-rules). a1 :- female. a1 :- m, a1. a1 :- m, a2. a2 :- male. a2 :- f, a1. a2 :- f, a2. The rule set for a1 is essentially (where f+ is the transitive closure of f), a1 :- female. a1 :- m, a1. a1 :- m, f+ , a1. a1 :- m, f* , male. Thus we have, a1 = (m U mf+)* (female U mf* male) = (mf*)* (female U mf* male) = (mf*)* female U (mf*)+ male = m(m U f)* female U female U m(m U f)* male = m(m U f)* (female U male) U female Let "parent = father U mother" and "person = female U male" a1 = mother parent* (person) U female. Similarly, we have a2 = father parent* (person) U male. That is, the rule is transformed to into a linear recursion as follows: female_ancestor(X, X) :- female(X). female_ancestor(X, Y) :- mother(X, Z), anc(Z, Y). male_ancestor(X, X) :- male(X). male_ancestor(X, Y) :- father(X, Z), anc(Z, Y). anc(X, X) :- female(X); male(X). anc(X, Y) :- (father(X, Z); mother(X, Z)), anc(Z, Y). It means that a person is a female_ancestor if she is a female or someone's or his/her ancestor's mother. It seems to me that method 2 gains both semantic cleaness and processing efficiency. It can be evaluated using the magic sets method or a typical transitive closure algorithm. However, the latter is more efficient that the former in deductive databases, which has been shown in Naughton's SIGMOD'88 paper on separable recursions. Please point it out if my understanding or any reasoning is wrong. Cheers - jiawei
raghu@ricotta.cs.wisc.edu (Raghu Ramakrishnan) (05/26/90)
(This is a somewhat technical message. The next para should let you skip the rest with little loss unless you have an enquiring mind that wants to know.) I think that mutual recursion is a red herring. The opportunity for the kind of optimization that Jiawei Han describes arises for different reasons. However, the example serves to illustrate some of the power of optimization techniques developed in the deductive database literature; you may want to simply compare the initial and optimized versions of the program below. Raghu --------------------------- In article <718@fornax.UUCP> han@fornax.UUCP (Jiawei Han) writes: > >The problem is whether it is worthwhile to compile a linear mutual >recursion and in what way we should compile it. > >We examine the same example. The original linear mutual recursion is > > female_ancestor(X, X) :- female(X). > female_ancestor(X, Y) :- mother(X, Z), female_ancestor(Z, Y). > female_ancestor(X, Y) :- mother(X, Z), male_ancestor(Z, Y). > > male_ancestor(X, X) :- male(X). > male_ancestor(X, Y) :- father(X, Z), female_ancestor(Z, Y). > male_ancestor(X, Y) :- father(X, Z), male_ancestor(Z, Y). > > ..... > >Method 2: Compile it based on the class of the linear mutual recursion. >We can simple present it in a short form as below (where we drop variables >in the predicates since the recursive rules are chain-rules). > > ...... > >That is, the rule is transformed to into a linear recursion as follows: > > female_ancestor(X, X) :- female(X). > female_ancestor(X, Y) :- mother(X, Z), anc(Z, Y). > > male_ancestor(X, X) :- male(X). > male_ancestor(X, Y) :- father(X, Z), anc(Z, Y). > > anc(X, X) :- female(X); male(X). > anc(X, Y) :- (father(X, Z); mother(X, Z)), anc(Z, Y). > >It seems to me that method 2 gains both semantic cleaness and processing >efficiency. It can be evaluated using the magic sets method or a typical >transitive closure algorithm. However, the latter is more efficient that >the former in deductive databases, which has been shown in Naughton's >SIGMOD'88 paper on separable recursions. >- jiawei Let us consider a query that binds the first argument, for concreteness. The original program can be automatically compiled into the following by directly applying magic sets and then ``factoring'' the resulting program: Program 1: m_fem_anc(jane). /* jane is the query constant */ m_fem_anc(Z) :- m_fem_anc(X), mother(X,Z). m_fem_anc(Z) :- m_mal_anc(X), father(X,Z). m_mal_anc(Z) :- m_fem_anc(X), mother(X,Z). m_mal_anc(Z) :- m_mal_anc(X), father(X,Z). fem_anc_2(X) :- m_fem_anc(X), female(X). fem_anc_2(Y) :- mal_anc_2(Y). mal_anc_2(X) :- m_mal_anc(X), male(X). The property of the program that makes this (O(n^2) to O(n), where n is the number of given facts) transformation possible is the left-linearity of the recursive rules; the mutual recursion does not play a role. Han's transformed program is also factorable. By applying magic sets and factoring, we get: Program 2: m_fem_anc(jane). /* jane is the query constant */ m_anc(Z) :- m_fem_anc(X), mother(X,Z). m_anc(Z) :- m_anc(X), father(X,Z). m_anc(Z) :- m_anc(X), mother(X,Z). fem_anc_2(X) :- m_fem_anc(X), female(X). fem_anc_2(Y) :- anc_2(Y). anc_2(X) :- m_anc(X), male(X). anc_2(X) :- m_anc(X), female(X). Programs 1 and 2 are both O(n), where n is the number of given facts; the original program and its magic sets version are both O(n^2). The difference between programs 1 and 2 is all that can be attributed to eliminating mutual recursion. (There is a difference, of course, and in this particular example, m_fem_anc and m_mal_anc seem likely to have a lot of overlap. Eliminating the mutual recursion between them aims to reduce the impact of this overlap.) (See "Arity Reduction by Factoring", by Naughton, Ramakrishnan, Sagiv and Ullman in VLDB 89 for more on factoring.)
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (06/06/90)
In article <718@fornax.UUCP>, han@fornax.UUCP (Jiawei Han) wrote
in response to my question about transforming mutual recursion.
The bottom line was that transforming mutual recursion into linear
recursion permits the use of a transitive closure algorithm.
The question is, when is this a good idea?
Consider a grammar written in Datalog. For example,
s --> np(P,N,nom), vp(P,N).
np(P,N,C) --> word(X), {pronoun(X,P,N,C)}.
np(3,N,_) --> word(the), word(X), {noun(X,N)}, opt_rel.
opt_rel --> word(that), vp(_,_).
opt_rel --> [].
vp(P,N) --> word(X), {intrans(X,P,N)}.
vp(P,N) --> word(X), {trans(X,P,N)}, np(_,_,acc).
Here the EDB would be the lexicon
pronoun(i, 1,s,nom).
pronoun(me, 1,s,acc).
...
and the input sentence
word(the,0,1). word(cat,1,2). word(that,2,3). ...
The top level query might be
?- s(0, X).
(Note that because I used word(X) instead of [X] there are no actual
lists involved.)
Here we have a mutual recursion between np//3, opt_rel//0, and vp//2.
I have no difficulty at all in believing that some bottom-up query
evaluators might be able to handle this parsing problem efficiently.
(If someone who is really at home with magic sets would care to explain
what the magic sets method would do with this example I would be grateful.)
But I don't see how turning the example into simple linear recursion would
help. _Would_ it?
The practical question is whether "real" mutual recursions are more like
my thinly-disguised ancestor example or more like this one. "Parts
explosion" is more like ancestor, but is that the most useful case?
--
"A 7th class of programs, correct in every way, is believed to exist by a
few computer scientists. However, no example could be found to include here."
warren@sbwarren.cs.sunysb.edu (David Scott Warren) (06/07/90)
In <3164@goanna.cs.rmit.oz.au>, ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) presents a Datalog example of a context-free grammar which includes mutual recursions and asks: >(If someone who is really at home with magic sets would care to explain >what the magic sets method would do with this example I would be grateful.) Not being an expert in magic sets, and at the risk of oversimplifying, I will try to give my intuitions about magic sets and grammars. The result of executing the ``Magic set''-transformed program bottom-up (semi-naive) is essentially the same as Extension Table (or OLDT) evaluation. (So one might think of Magic-sets as a ``compilation'' technique for extension tables.) And Extension Table evaluation is essentially the same as Earley Deduction. And Earley Deduction on Datalog grammars as Richard presented is Earley's context-free recognition algorithm. So by transitivity of ``essentially the same as'', magic sets on Datalog grammars is Earley recognition. Evidence for the validity of this claim is Raghu Ramakrishnan's comment that under magic sets, left-recursion is linear and right-recursion is quadratic, exactly as it is in Earley recognition. David S. Warren warren@sbcs.sunysb.edu
han@fornax.UUCP (Jiawei Han) (06/07/90)
In article <3164@goanna.cs.rmit.oz.au>, ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) writes: > In article <718@fornax.UUCP>, han@fornax.UUCP (Jiawei Han) wrote > in response to my question about transforming mutual recursion. > The bottom line was that transforming mutual recursion into linear > recursion permits the use of a transitive closure algorithm. > > The question is, when is this a good idea? > > Consider a grammar written in Datalog. For example, > s --> np(P,N,nom), vp(P,N). > np(P,N,C) --> word(X), {pronoun(X,P,N,C)}. > np(3,N,_) --> word(the), word(X), {noun(X,N)}, opt_rel. > opt_rel --> word(that), vp(_,_). > opt_rel --> []. > vp(P,N) --> word(X), {intrans(X,P,N)}. > vp(P,N) --> word(X), {trans(X,P,N)}, np(_,_,acc). > > Here the EDB would be the lexicon > pronoun(i, 1,s,nom). > pronoun(me, 1,s,acc). > ... > and the input sentence > word(the,0,1). word(cat,1,2). word(that,2,3). ... > > The top level query might be > ?- s(0, X). > ...... This is another interesting exmaple to show the compilation method works well. Let me try it. I write the grammar as follows. s :- np(P,N,nom), vp(P,N). np(P,N,C) :- pronoun(X,P,N,C). np(P,N,_) :- word(the), noun(X,N), opt_rel. opt_rel :- word(that), vp(_,_). opt_rel :- []. vp(P,N) :- intrans(X,P,N). vp(P,N) :- trans(X,P,N), np(_,_,acc). The transformation can be performed as follows: In this rule set, the mutually recursive predicates are np//3 and vp//2. We first remove the intermediate predicate: opt_rel//0. s :- np(P,N,nom), vp(P,N). np(P,N,C) :- pronoun(X,P,N,C). np(P,N,_) :- word(the), noun(X,N). np(P,N,_) :- word(the), noun(X,N), word(that), vp(_,_). vp(P,N) :- intrans(X,P,N). vp(P,N) :- trans(X,P,N), np(_,_,acc). Clearly, np is defined by the following rule set (with no mutual recursion): np(P,N,C) :- pronoun(X,P,N,C). np(P,N,_) :- word(the), noun(X,N). np(P,N,_) :- word(the), noun(X,N), word(that), intrans(Y,P,M). np(P,N,_) :- word(the), noun(X,N), word(that), trans(Y,P,M), np(_,_,acc). Then there is no difficulty to compile the rule set into a transitive closure: exit-portion(C) = pronoun(X,P,N,C) U (word(the), noun(X,N)) U (word(the), noun(X,N), word(that), intrans(Y,P,M) np(P,N,norm) = exit-portion(norm) U ((word(the), noun(X,N'), word(that), trans(Y,P',M))+ , exit-portion(acc)). Similarly, we can derive the formula for vp. Therefore, s can be generated accordingly, which can be processed by transitive closure algorithms. Actually, not all the linear mutual recursions are transformable into transitive closure operations although I believe more than 90% application examples are. Even if they are not, they should be able to be transformed into relatively simple linear recursive programs containing no mutually recursive predicates (as I discussed in my technical report). I wish that I can get a counter-example (real example, not those can never find semanitc explanations) to show that my method may have some difficulties. -jiawei
debray@cs.arizona.edu (Saumya K. Debray) (06/07/90)
Richard A. O'Keefe (ok@goanna.cs.rmit.oz.au) writes: > In article <718@fornax.UUCP>, han@fornax.UUCP (Jiawei Han) wrote > in response to my question about transforming mutual recursion. > The bottom line was that transforming mutual recursion into linear > recursion permits the use of a transitive closure algorithm. > > The question is, when is this a good idea? This is probably tangential to Richard's point, but there are transitive closure algorithms that require O(log N) iterations to compute the fixpoint, where the (semi)naive algorithm for fixpoint computation would require O(N) iterations. (There is, however, some growth in code size.:-) -- Saumya Debray CS Department, University of Arizona, Tucson internet: debray@cs.arizona.edu uucp: uunet!arizona!debray
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (06/08/90)
In article <3164@goanna.cs.rmit.oz.au>, I wrote > Consider a grammar written in Datalog. For example, > s --> np(P,N,nom), vp(P,N). > np(P,N,C) --> word(X), {pronoun(X,P,N,C)}. ... In article <792@fornax.UUCP>, han@fornax.UUCP (Jiawei Han) missed the point completely. > Let me try it. I write the grammar as follows. > s :- np(P,N,nom), vp(P,N). > np(P,N,C) :- pronoun(X,P,N,C). ... I used definite clause grammar notation (and referred to non-terminals with N arguments as P//N rather than to predicates with N+2 arguments as P/(N+2)) because I was writing a grammar. The first two clauses transliterate to s(S0,S) :- np(P, N, nom, S0, S1), vp(P, N, S1, S). If the method works well with what I actually wrote, it's interesting. If it works with some random thing with no sequence arguments, it isn't. Let me repeat the grammar, this time with the sequence arguments explicit, and one more rule which I accidentally forgot. s(S0,S) :- np(P,N,nom,S0,S1), vp(P,N,S1,S). np(P,N,C,S0,S) :- word(X,S0,S), pronoun(X,P,N,C). np(3,N,_,S0,S) :- word(the,S0,S1), word(X,S1,S2), noun(X,N), opt_rel(S2,S). opt_rel(S0,S) :- word(that,S0,S1), vp(_,_,S1,S). opt_rel(S0,S) :- word(that,S0,S1), np(P,N,nom,S1,S2), tv(P,N,S2,S). opt_rel(S,S). vp(P,N,S0,S) :- word(X,S0,S), intrans(X,P,N). vp(P,N,S0,S) :- tv(P,N,S0,S1), np(_,_,acc,S1,S). tv(P,N,S0,S) :- word(X,S0,S), trans(X,P,N). The rule which had been omitted lets us say "the rat that the cat chased" as well as "the cat that chased the rat", and it really made me look stupid leaving it out because the _point_ of my example was to show something that was naturally recurring in a "tree-structured" fashion. -- "A 7th class of programs, correct in every way, is believed to exist by a few computer scientists. However, no example could be found to include here."
han@fornax.UUCP (Jiawei Han) (06/09/90)
In article <3191@goanna.cs.rmit.oz.au>, ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) writes: ...... > Let me repeat the grammar, this time with the sequence arguments > explicit, and one more rule which I accidentally forgot. > > s(S0,S) :- np(P,N,nom,S0,S1), vp(P,N,S1,S). > > np(P,N,C,S0,S) :- word(X,S0,S), pronoun(X,P,N,C). > np(3,N,_,S0,S) :- word(the,S0,S1), word(X,S1,S2), > noun(X,N), opt_rel(S2,S). > > opt_rel(S0,S) :- word(that,S0,S1), vp(_,_,S1,S). > opt_rel(S0,S) :- word(that,S0,S1), np(P,N,nom,S1,S2), tv(P,N,S2,S). > opt_rel(S,S). > > vp(P,N,S0,S) :- word(X,S0,S), intrans(X,P,N). > vp(P,N,S0,S) :- tv(P,N,S0,S1), np(_,_,acc,S1,S). > > tv(P,N,S0,S) :- word(X,S0,S), trans(X,P,N). > > The rule which had been omitted lets us say "the rat that the cat chased" > as well as "the cat that chased the rat", and it really made me look > stupid leaving it out because the _point_ of my example was to show > something that was naturally recurring in a "tree-structured" fashion. > ...... I am not an expert on natural language processing. I am really sorry for not transforming fully your grammar into Prolog (the states S0, S1, etc. were missing). Let me try this new set of rules. The newly added rule makes the recursion not a single linear recursion but a multiple linear one. The "np" portion is transformed into a multiple linear recursion (without mutually recursive predicates) as follows. s(S0,S) :- np(P,N,nom,S0,S1), vp(P,N,S1,S). np(P,N,C,S0,S) :- word(X,S0,S), pronoun(X,P,N,C). np(3,N,_,S0,S) :- word(the,S0,S1), word(X,S1,S), noun(X,N). np(3,N,_,S0,S) :- word(the,S0,S1), word(X,S1,S2), noun(X,N), word(that,S2,S3), word(Y,S3,S), intrans(Y,P,M). np(3,N,_,S0,S) :- word(the,S0,S1), word(X,S1,S2), noun(X,N), word(that,S2,S3), word(Y,S3,S4), trans(Y,P,M), np(_,_,acc,S4,S). np(3,N,_,S0,S) :- word(the,S0,S1), word(X,S1,S2), noun(X,N), word(that,S2,S3), np(P,N1,nom,S3,S4), word(Y,S4,S), trans(Y,P1,N2). Similarly for "vp" portion as well. I understand the above "np" rule set as follows: np(_,_) :- Pronoun(X). np(3,_) :- "the", Noun(X). np(3,_) :- "the", Noun(X), "that", Intrans(Y). np(3,_) :- "the", Noun(X), "that", Trans(Y), np(_,acc). np(3,_) :- "the", Noun(X), "that", np(_,nom), Trans(Y). (Notice that in the real execution, we must associate it with all the proper states to make it complete. 3 means that all the generated Noun or Pronoun should be the third person, if my understanding is right). As I discussed in my "Processing Multiple Linear Recursions" paper, this multiple linear recursion cannot be evaluated by a transitive closure algorithm. However, it can be evaluated by side-relation unioned processing. We take the exit rule as e :- [Pronoun(X)] U ["the", Noun(X)] U ["the", Noun(X), "that", Intrans(Y)]. and the left portions of the second linear recursive rule as l2 :- "the", Noun(X), "that". The rule set is equivelent to, (Sorry for further simplification, we could associate more states but the results should be essentially the same). np(_) :- e. np(_) :- l2, Trans(Y), np(acc). np(_) :- l2, np(nom), Trans(Y). The trick in the evaluation of this multiple linear recursion is that every time after we go through the portion ["the", Noun(X), "that"], we set a flag [2]. If we go through one more portion [Trans(Y)], the flag is switched to [1]. Flag [1] should not have a matched Trans(Y) after np(acc) but Flag [2] needs the match of a Trans(Y) after np(nom). So the evaluation is quite close to the evaluation of a two-sided single linear recursion (e.g., the same-generation problem). That is, we may use the counting method or the magic sets method and treat it as if it were a single linear recursion. This is another good example which shows why deductive DB people want to study something beyond transitive closure (e.g. the same-generation recursion) in the evaluation of linear recursions. This is a very interesting example. Thanks. -jiawei