I've got a library that I have to interface with which acts basically as a data source. When retrieving data, I can pass special "filter expressions" to that library, which later get translated to SQL WHERE part. These expressions are pretty limited. They must be in conjunctive normal form. Like:
(A or B or C) and (D or E or F) and ...
This of course isn't very comfortable for programming. So I want to make a little wrapper which can parse arbitrary expressions and translate them to this normal form. Like:
(A and (B or C) and D) or E
would get translated to something like:
(A or E) and (B or C or E) and (D or E)
I can parse an expression to a tree with the Irony library. Now I need to normalize it, but I don't know how... Oh, also, here's the twist:
The final expression may not contain the NOT operator. However, I can inverse the individual terms by replacing the operators with the inverse operators. So, this is OK:(not A or not B) AND (not C or not D)but this is not:
not (A or B) and not (C or D)
I would like to make the expression as simple as possible, because it will be translated to a practically identical SQL WHERE statement, so a complex statement would most likely slow down execution speed.
I'd use two iterations over the tree, although it's probably possible in one.
First iteration: get rid of your NOT Nodes by walking through the tree and using De Morgan's law (wikipedia link) and remove double negation wherever applicable.
Second iteration (the NOT are now only directly before a leaf node)
Go through your tree:
Case "AND NODE":
fine, inspect the children
Case "OR NODE":
if there is a child which is neither a Leaf nor a NOT node
apply the distributive law.
start from parent of current node again
else
fine, inspect children
After that you should be done.
Related
When attempting to solve logic problems on a computer, it is usual to first convert them to CNF, because the best solving algorithms expect CNF as input.
For propositional logic, the textbook rules for this conversion are simple, but if you apply them as is, the result is one of the very rare cases where a program encounters double exponential resource consumption without being specifically constructed to do so:
a <=> (b <=> (c <=> ...))
with N variables, generates 2^2^N clauses, one exponential blowup in the conversion of equivalence to AND/OR, and another in the distribution of OR into AND.
The solution to this is to rename subterms. If we rewrite the above as something like
r <=> (c <=> ...)
a <=> (b <=> r)
where r is a fresh symbol that is being defined to be equal to a subterm - in general, we may need O(N) such symbols - the exponential blowups can be avoided.
Unfortunately, this runs into a problem when we try to extend it to first-order logic. Using TPTP notation where ? means 'there exists' and variables begin with capital letters, consider
a <=> ?[X]:p(X)
Admittedly this case is simple enough that there is no actual need to rename the subterm, but it's necessary to use a simple case for illustration, so suppose we are using an algorithm that just automatically renames arguments of the equivalence operator; the point generalizes to more complex cases.
If we try the above trick and rename the ? subterm, we get
r <=> ?[X]:p(X)
Existential variables are converted to Skolem symbols, so that ends up as
r <=> p(s)
The original formula then expands to
(~a | r) & (a | ~r)
Which is by construction equivalent to
(~a | p(s)) & (a | ~p(s))
But this is not correct! Suppose we had not done the renaming, but just expanded the original formula as it was, we would get
(~a | ?[X]:p(X)) & (a | ~?[X]:p(X))
(~a | ?[X]:p(X)) & (a | ![X]:~p(X))
(~a | p(s)) & (a | ~p(X))
which is critically different from the version we got with the renaming.
The problem is that equivalence needs both the positive and negative versions of each argument, but applying negation to terms that contain universal or existential quantifiers, structurally changes those terms; you cannot just encapsulate them in a definition, then apply the negation to the defined symbol.
The upshot of this is that when you have equivalence and the arguments may contain such quantifiers, you actually need to recur through each argument twice, once for the positive version, once for the negative. This suffices to bring back the existential blowup we hoped to avoid by doing the renaming. As far as I can see, this problem is not caused by the way a particular algorithm works, but by the nature of the task.
So my question:
Given an input formula that may contain arbitrary nesting of equivalence and quantifiers, is there any algorithm that will correctly turn this to CNF with a polynomial rather than exponential number of clauses?
As you observed, an existential such as ∃X.p(X) is not in fact equivalent to a Skolemized expression p(S). Its negation ¬∃X.p(X) is not equivalent to ¬p(S), but to ∀Y.¬p(Y).
Possible approaches that avoid the exponential blow-up:
Convert existentials such as ∃X.p(X) to universals such as ¬∀Y.p(Y), or vice versa, so you have a canonical form. Skolemize at a later step.
Remember when you convert that your p(S) is a Skolemized existential, and that its negation is ∀Y.¬p(Y).
Define terms equivalent to universals and existentials, such that a represents ∀Y.p(Y) and ¬a then represents ¬∀Y.p(Y), or equivalently, ∃X.¬p(X).
Use the symmetry of Boolean duals, so that the same transformations apply with AND and OR swapped, De Morgan’s Laws, and the equivalence between existentials and negated universals, to restore the symmetry between the expansions of r and ~r. The negations in the conversion between universals and existentials and in De Morgan's Laws cancel each other out, and the duality of switching AND and OR means you can re-use the result on the left to generate the one on the right mechanically again?
Given that you need to support ALL and NOT ALL statements anyway, this should not create any new problems. Just canonicalize and use the same approach you would for a universal.
If you’re solving by converting to SAT, your terms can represent universals, too. So, in your example, you’re trying to replace a with r, but you can still use ~a, equivalent to the negative universal.
In your expressions. you’d still use (~a | r) & (a | ~r), but expand ~r to its correct rather than the incorrect value. That example is trivial, since that’s just ~a, but you’d normally define r as equivalent to a more complex transformation, and in that case you need to remember what both r and ~r represent. It is not really a simple mechanical transformation of the Skolemized expression.
In this example, I’m not sure why it’s a problem that (~a | r) & (a | ~r) is equivalent to (~a | r) & (a | ~a), which simplifies to (~a | r). That’s not going to give you exponential blow-up? When you translate back to first-order predicate logic, make the correct translation.
Update
Thanks for clarifying what the problem was in chat. As I currently think I understand it, what you have is an equivalence with a left and a right side, which contains other nested equivalences, and you want to expand both the equivalence and its negation. The problem is that, because the negation does not have symmetrical form, you need to recurse twice for each nested equivalence in the tree, once when expanding the equivalence and once when expanding its negation?
You should define a transformation that generates the negative expansion from the positive expansion in linear time, and divide-and-conquer the expressions containing nested equivalences using that. This seems to be what you were after with the ~p(S) transformation.
To do this, you recall that ¬∃X.p(X) is equivalent to ∀X.¬p(X), and vice versa. Then if you’ve expanded p(x) into normal form as conjunctions and disjunctions, De Morgan’s Laws lets you turn an expression like ¬(a ∨ ¬b) into ¬a ∧ b. The inner ¬ on the quantifier transformation and the outer ¬ on the De Morgan transformation cancel each other out. Finally, the dual of any Boolean equivalence remains valid when you replace each ∨ and ∧ with the other and any atom a or ¬a with its inverse.
So, while I might be making an error, especially at 1 AM, it looks to me like what you want is the dual transformation that substitutes:
An outer ∃ for ∀ and vice versa
∧ for ∨ and vice versa
Each term t with ¬t and vice versa
Apply this to the expansion of the positive equivalence to generate the negative dual in time proportional to its length, without further recursion.
In terms of if conditions in programming languages. I do not remember this stuff so I am asking.
I know what (A OR B OR (C AND D)) means
but what does (A OR B OR C AND D) mean?
In general, AND takes precedence over OR. But like the comments said, it can depend on the language. For more information on order of operations, see: http://en.wikipedia.org/wiki/Order_of_operations
Another reference, that is Excel specific, also states AND precedes OR in the order of operations. See here: http://office.microsoft.com/en-us/excel-help/HV080557451.aspx
EDIT:
What this means is that your two statements are equal.
(A OR B OR C AND D)
is evaluated as:
(A OR B OR (C AND D))
So I have the following working code in Prolog that produces the factorial of a given value of A:
factorial(0,1).
factorial(A,B) :- A>0, C is A-1, factorial(C,D), B is A*D.
I am looking for an explanation as to how this code works. I.e, what exactly happens when you ask the query: factorial(4, Answer).
Firstly,
factorial(0, 1).
I know the above is the "base case" of the recursive definition. What I am not sure of why/how it is the base case. My guess is that factorial(0, 1) inserts some structure containing (0, 1) as a member of "factorial". If so, what does the structure look like? I know if we say something like "rainy(seattle).", this means that Seattle is rainy. But "factorial(0, 1)"... 0, 1 is factorial? I realize it means factorial of 0 is 1, but how is this being used in the long run? (Writing this is helping me understand more as I go along, but I would like some feedback to make sure my thinking is correct.)
factorial(A,B) :- A>0, C is A-1, factorial(C,D), B is A*D.
Now, what exactly does the above code mean. How should I read it?
I am reading it as: factorial of (A, B) is true if A>0, C is A-1, factorial(C, D), B is A*D. That does not sound quite right to me... Is it?
"A > 0". So if A is equal to 0, what happens? It must not return at this point, or else the base case would never be used. So my guess is that A > 0 returns false, but the other functions are executed one last time. Did recursion stop because it reached the base case, or because A was not greater than 0? Or a combination of both? At what point is the base case used?
I guess that boils down to the question: What is the purpose of having both a base case and A > 0?
Sorry for the badly formed questions, thank you.
EDIT: In fact, I removed "A > 0" from the procedure and the code still works. So I guess my questions were not stupid at least. (And that code was taken from a tutorial.)
It is counterproductive to think of Prolog facts and rules in terms of data structures. When you write factorial(0, 1). you assert a fact to the Prolog interpreter that is assumed to be universally true. With this fact alone Prolog can answer questions of three types:
What is the factorial of 0? (i.e. factorial(0, X); the answer is X=1)
A factorial of what number is 1? (i.e. factorial(X,1); the answer is X=0)
Is it true that a factorial of 0 is 1? (i.e. factorial(0,1); the answer is "Yes")
As far as the rest of your Prolog program is concerned, only the first question is important. That is the question that the second clause of your factorial/2 rule will be asking at the end of evaluating a factorial.
The second rule uses comma operator, which is Prolog's way of saying "and". Your interpretation can be rewritten in terms of variables A and B like this:
B is a factorial of A when A>0, and C is set to A-1, and D is set to the factorial of C, and B is set to A times D
This rule covers all As above zero. The reference to factorial(C,D) will use the same rule again and again, until C arrives to zero. This is when this rule stops being applicable, so Prolog would grab the "base case" rule, and use 1 as its output. At this point, the chain of evaluating factorial(C, D) starts unwrapping, until it goes all the way to the initial invocation of the rule. This is when Prolog computes the final answer, and factorial/2 returns "Yes" and produces the desired output value.
In response to your edit, removing the A>0 is not dangerous only for getting the first result. Generally, you can ask Prolog to find you more results. This is when the factorial/2 with A>0 removed would fail spectacularly, because it would start going down the invocation chain of the second clause with negative numbers - a chain of calls that will end in numeric overflow or stack overflow, whichever comes first.
If you come from a procedural language background, the following C++ code might help. It mirrors pretty accurately the way the Prolog code executes (at least for the common case that A is given and B is uninstantiated):
bool fac(int a, int &b)
{
int c,d;
return
a==0 && (b=1,true)
||
a>0 && (c=a-1,true) && fac(c,d) && (b=a*d,true);
}
The Prolog comma operates like the sequential &&, and multiple clauses like a sequential ||.
My mental model for how prolog works is a tree traversal.
The facts and predicates in a prolog database form a forest of trees. When you ask the Prolog engine to evaluate a predicate:
?- factorial(6,N).
the Prolog engine looks for the tree rooted with the specified functor and arity (factorial/2 in this case). The Prolog engine then performs a depth-first traversal of that tree trying to find a solution using unification and pattern matching. Facts are evaluated as they are; For predicates, the right-hand side of the :- operator is evaluated, walking further into the tree, guided by the various logical operators.
Evaluation stops with the first successful evaluation of a leaf node in the tree, with the prolog engine remembering its state in the tree traversal. On backtracking, the tree traversal continues from where it left off. Execution is finally complete when the tree traversal is completed and there are no more paths to follow.
That's why Prolog is a descriptive language rather than an imperative language: you describe what constitutes truth (or falsity) and let the Prolog engine figure out how to get there.
I have to write a program that tests whether two algebraic expressions are equivalent. It should follow MDAS precedence and parenthesis grouping. To solve the problem about precedence, I'm thinking I should implement a Infix to Postfix Notation converter for these expressions. But by doing this, I could not conclude their equivalence.
The program should look like this:
User Input: a*(a+b) = a*a + a*b
Output : Equivalent
For this problem I'm not allowed to use Computer Algebraic Systems or any external libraries. Please don't post the actual code if you have one, I just need an idea to work this problem out.
If you are not allowed to evaluate the expressions, you will have to parse them out into expression trees.
After that, I would get rid of all parenthesis by multiplying/dividing all members so a(b - c) becomes a*b - a*c.
Then convert all expressions back to strings, making sure you have all members alphabetically sorted (a*b, not b*a) ,remove all spaces and compare strings.
That's an idea:
You need to implement building expression tree first because it's a very natural representation of expression.
Then maybe you'll need to simplify it by open brackets and etc. using associative or distributive algebraic properties.
Then you'll have to compare trees. It's not obvious because you need to take care of all branch permutations in commutative operations and etc. E.g. you can sort them (I mean branches) and then compare for equality. Also you need to keep in mind possible renaming of parameters, i.e. a + b need to be equal x + y.
I don't know haskell syntax, but I know some FP concepts (like algebraic data types, pattern matching, higher-order functions ect).
Can someone explain please, what does this code mean:
data Tree ? = Leaf ? | Fork ? (Tree ?) (Tree ?)
rotateR tree = case tree of
Fork q (Fork p a b) c -> Fork p a (Fork q b c)
As I understand, first line is something like Tree-type declaration (but I don't understand it exactly). Second line includes pattern matching (I don't understand as well why do we need to use pattern matching here). And third line does something absolutely unreadable for non-haskell developer. I've found definition of Fork as fork (f,g) x = (f x, g x) but I can't move further anymore.
First of all the data type definition should not contain question marks, but normal letters:
data Tree a = Leaf a | Fork a (Tree a) (Tree a)
It defines a type Tree that contains elements of some not further specified type a.
The tree is either a Leaf, containing an element of type a, or it is a Fork, containing also an element of type a and two subtrees. The subtrees are Tree structures that contain elements of type a.
Important to note is that Haskell uses parenthesis purely for grouping, like in 2 * (2+3), not to specify calling functions. To call functions, the parameters are just written after the function name, separated with spaces, like in sin 30 or compare "abc" "abd".
In the case statement, the part to the left of -> is a pattern match, the part to the right is the functions result in case the tree actually had the form specified on the left. The pattern Fork q (Fork p a b) c matches if the tree is a Fork (that's the Fork from the data type definition) and the first subtree of it is another Fork. The lowercase letters are all just variables, capturing the different parts of the tree structure matched. So p would be the element contained in the subtree, a would be the subtrees first branch and b the second one.
The right side of the ->, Fork p a (Fork q b c), now builds a new tree from these parts matched in the pattern match. The lower case variables are all the tree parts matched on the left, and the Forks are the constructors from the data type definition. It build a tree that is a Fork and has a second subtree that is also a Fork (the part in parenthesis). The remaining pieces of this tree are just the parts of the tree that has been "dissolved" on the left side.
I think you misunderstand Fork. It is not a function, but a constructor for type Tree. It is essentially a node in the Tree data structure... Each node in Tree is either a Leaf (with a value) or a Fork (with a value and two sub-nodes).
Pattern matching is used to transform the structure. My ASCII art is not good enough to give you a drawing, but it sort-of moves 'left nodes' up and 'right nodes' down.
Note: I say you may be misunderstanding Fork, because fork (f,g) x = (f x, g x) is something completely different. It is a higher order function in this case and has nothing to do with your Tree structure.
Hope that helps :),
Carl