Tool to solve propositional logic / boolean expressions (SAT Solver?) - logic

I am new to the topic of propositional logic and boolean expressions. So this is why I need help. Here is my problem:
In the car industry you have thousand of different variants of components available to choose from when you buy a car. Not every component is combinable, so for each car there exist a lot of rules that are expressed in propositional logic. In my case each car has between 2000 and 4000 rules.
They look like this:
A → B ∨ C ∨ D
C → ¬F
F ∧ G → D
...
where "∧" = "and" / "∨" = "or" / "¬" = "not" / "→" = "implication".
The variables A, B, C, ... are linked to the components in the bill of material. The data I have consists of pairs of components with their linked variables.
Example:
Component_1, Component_2: (A) ∧ (B)
Component_1, Component_3: (A) ∧ (C ∨ F)
Component_3, Component_5: (B ∨ G)
...
Now, my question is how to solve this problem. Specifically, I would like to know if each combination of the components is possible according to rules above.
Which tool, software and algorithm can solve these type of problems?
Is there a illustrative example?
How can I automate it, so I can check each combination in my list?
Generally, what should I search for in Google to deepen my knowledge in this topic?
Thank you very much for your help!
Olaf

You might want to try a Prolog system with a SAT Solver, such as SWI-Prolog, Jekejeke Minlog, etc... you can readily play with it in the REPL of the Prolog system. To load the SAT solver just type (you don't need to type the ?- itself):
/* in SWI-Prolog */
?- use_module(library(clpb)).
/* in Jekejeke Minlog */
?- use_module(library(finite/clpb)).
You can then use the top-level to search for solutions of a boolean formula, like this example here an xor:
?- sat(X#Y), labeling([X,Y]).
X = 0,
Y = 1 ;
X = 1,
Y = 0.
Here is an example of a kitchen planner code. The kitchen has 3 places,
and we assign a freezer and a stove. The freezer is not allowed to be near to the stove.
The freezer has code 0,1 and the stove has code 1,0. We make use of the card constraint to place the freezer and the stove.
:- use_module(library(finite/clpb)).
freezer([X,Y|L],[(~X)*Y|R]) :-
freezer(L, R).
freezer([], []).
stove([X,Y|L],[X*(~Y)|R]) :-
stove(L, R).
stove([], []).
free([X,Y|L],[(~X)*(~Y)|R]) :-
free(L, R).
free([], []).
allowed([X,Y,Z,T|L]) :-
sat(~((~X)*Y*Z*(~T))),
sat(~(X*(~Y)*(~Z)*T)),
allowed([Z,T|L]).
allowed([_,_]).
allowed([]).
kitchen(L) :-
freezer(L, F), card(1, F),
stove(L, G), card(1, G),
free(L, H), card(1, H),
allowed(L).
What I want to demonstrate with the Prolog code is the benefit, that problem encoding towards a SAT formulation can be done via Prolog code itself. When the above code is run I get the following result as expected:
?- L=[_,_,_,_,_,_], kitchen(L), labeling(L).
L = [0,1,0,0,1,0] ;
L = [1,0,0,0,0,1] ;
No

Related

Extracting symbols from a given formula

I'm trying to extract symbols from formula. Ex:
?- formula((p v q) & (q v r), U).
U = [p, q, v].
What I've done so far:
simbol_formula([],[]).
simbol_formula(negation X, [X]).
simbol_formula(X or Y, [X,Y]).
simbol_formula(X and Y, [X,Y]).
I believe what I did is correct but incomplete. I am stuck. It obviously works for simple formulas but for more complex formulas it does not. I know I have to define something as simbol_formula(F,U) :-. Using recursion somehow or break the given formula into "smaller" ones.
The core problem in your case is the use of defaulty data structures.
In your representation, you cannot, by pattern matching alone, distinguish between:
a symbol
other formulas.
To overcome this shortcoming, I suggest to uniquely mark symbols with the (arbitrary) functor s/1.
For example, the formula (p v q) & (q v r) would be represented as:
(s(p) ∨ s(q)) & (s(q) ∨ s(r))
Then, we can use a DCG to relate formulas to symbols:
symbols(s(X)) --> [X].
symbols(negation(F)) --> symbols(F).
symbols(X ∨ Y) --> symbols(X), symbols(Y).
symbols(X & Y) --> symbols(X), symbols(Y).
Sample query:
?- phrase(symbols((s(p) ∨ s(q)) & (s(q) ∨ s(r))), Ls).
Ls = [p, q, q, r].
I leave defining the suitable operators as an exercise, so that the above compiles.
The above can also be used to enumerate formulas, albeit unfairly:
?- phrase(symbols(Formula), Ls).
Formula = s(_G1010),
Ls = [_G1010] ;
Formula = negation(s(_G1012)),
Ls = [_G1012] ;
Formula = negation(negation(s(_G1014))),
Ls = [_G1014] .

Computer Reasoning about Prologish Boolos' Curious Inference

Boolo's curious inference has been originally formulated with equations here. It is a recursive definition of a function f and a predicate d via the syntax of N+, the natural numbers without zero, generated from 1 and s(.).
But it can also be formulated with Horn Clauses. The logical content is not exactly the same, the predicate f captures only the positive aspect of the function, but the problem type is the same. Take the following Prolog program:
f(_, 1, s(1)).
f(1, s(X), s(s(Y))) :- f(1, X, Y).
f(s(X), s(Y), T) :- f(s(X), Y, Z), f(X, Z, T).
d(1).
d(s(X)) :- d(X).
Whats the theoretical logical outcome of the last query, and can you demonstrably have a computer program in our time and space that produces the outcome, i.e. post the program on gist and everybody can run it?
?- f(X,X,Y).
X = 1,
Y = s(1)
X = s(1),
Y = s(s(s(1)))
X = s(s(1)),
Y = s(s(s(s(s(s(s(s(s(s(...))))))))))
ERROR: Out of global stack
?- f(s(s(s(s(1)))), s(s(s(s(1)))), X), d(X).
If the program that does the job of certifying the result is not a Prolog interpreter itself like here, what would do the job especially suited for this Prologish problem formulation?
One solution: Abstract interpretation
Preliminaries
In this answer, I use an interpreter to show that this holds. However, it is not a Prolog interpreter, because it does not interpret the program in exactly the same way Prolog interprets the program.
Instead, it interprets the program in a more abstract way. Such interpreters are therefore called abstract interpreters.
Program representation
Critically, I work directly with the source program, using only modifications that we, by purely algebraic reasoning, know can be safely applied. It helps tremendously for such reasoning that your source program is completely pure by construction, since it only uses pure predicates.
To simplify reasoning about the program, I now make all unifications explicit. It is easy to see that this does not change the meaning of the program, and can be easily automated. I obtain:
f(_, X, Y) :-
X = 1,
Y = s(1).
f(Arg, X, Y) :-
Arg = 1,
X = s(X0),
Y = s(s(Y0)),
f(Arg, X0, Y0).
f(X, Y, T) :-
X = s(X0),
Y = s(Y0),
f(X, Y0, Z),
f(X0, Z, T).
I leave it as an easy exercise to show that this is declaratively equivalent to the original program.
The abstraction
The abstraction I use is the following: Instead of reasoning over the concrete terms 1, s(1), s(s(1)) etc., I use the atom d for each term T for which I can prove that d(T) holds.
Let me show you what I mean by the following interpretation of unification:
interpret(d = N) :- d(N).
This says:
If d(N) holds, then N is to be regarded identical to the atom d, which, as we said, shall denote any term for which d/1 holds.
Note that this differs significantly from what an actual unification between concrete terms d and N means! For example, we obtain:
?- interpret(X = s(s(1))).
X = d.
Pretty strange, but I hope you can get used to it.
Extending the abstraction
Of course, interpreting a single unification is not enough to reason about this program, since it also contains additional language elements.
I therefore extend the abstract interpretation to:
conjunction
calls of f/3.
Interpreting conjunctions is easy, but what about f/3?
Incremental derivations
If, during abstract interpretation, we encounter the goal f(X, Y, Z), then we know the following: In principle, the arguments can of course be unified with any terms for which the goal succeeds. So we keep track of those arguments for which we know the query can succeed in principle.
We thus equip the predicate with an additional argument: A list of f/3 goals that are logical consequences of the program.
In addition, we implement the following very important provision: If we encounter a unification that cannot be safely interpreted in abstract terms, then we throw an error instead of failing silently. This may for example happen if the unification would fail when regarded as an abstract interpretation although it would succeed as a concrete unification, or if we cannot fully determine whether the arguments are of the intended domain. The primary purpose of this provision is to avoid unintentional elimination of actual solutions due to oversights in the abstract interpreter. This is the most critical aspect in the interpreter, and any proof-theoretic mechanism will face closely related questions (how can we ensure that no proofs are missed?).
Here it is:
interpret(Var = N, _) :-
must_be(var, Var),
must_be(ground, N),
d(N),
Var = d.
interpret((A,B), Ds) :-
interpret(A, Ds),
interpret(B, Ds).
interpret(f(A, B, C), Ds) :-
member(f(A, B, C), Ds).
Quis custodiet ipsos custodes?
How can we tell whether this is actually correct? That's the tough part! In fact, it turns out that the above is not sufficient to be certain to catch all cases, because it may simply fail if d(N) does not hold. It is obviously not acceptable for the abstract interpreter to fail silently for cases it cannot handle. So we need at least one more clause:
interpret(Var = N, _) :-
must_be(var, Var),
must_be(ground, N),
\+ d(N),
domain_error(d, N).
In fact, an abstract interpreter becomes a lot less error-prone when we reason about ground terms, and so I will use the atom any to represent "any term at all" in derived answers.
Over this domain, the interpretation of unification becomes:
interpret(Var = N, _) :-
must_be(ground, N),
( var(Var) ->
( d(N) -> Var = d
; N = s(d) -> Var = d
; N = s(s(d)) -> Var = d
; domain_error(d, N)
)
; Var == any -> true
; domain_error(any, Var)
).
In addition, I have implemented further cases of the unification over this abstract domain. I leave it as an exercise to ponder whether this correctly models the intended semantics, and to implement further cases.
As it will turn out, this definition suffices to answer the posted question. However, it clearly leaves a lot to be desired: It is more complex than we would like, and it becomes increasingly harder to tell whether we have covered all cases. Note though that any proof-theoretic approach will face closely corresponding issues: The more complex and powerful it becomes, the harder it is to tell whether it is still correct.
All derivations: See you at the fixpoint!
It now remains to deduce everything that follows from the original program.
Here it is, a simple fixpoint computation:
derivables(Ds) :-
functor(Head, f, 3),
findall(Head-Body, clause(Head, Body), Clauses),
derivables_fixpoint(Clauses, [], Ds).
derivables_fixpoint(Clauses, Ds0, Ds) :-
findall(D, clauses_derivable(Clauses, Ds0, D), Ds1, Ds0),
term_variables(Ds1, Vs),
maplist(=(any), Vs),
sort(Ds1, Ds2),
( same_length(Ds2, Ds0) -> Ds = Ds0
; derivables_fixpoint(Clauses, Ds2, Ds)
).
clauses_derivable(Clauses, Ds0, Head) :-
member(Head-Body, Clauses),
interpret(Body, Ds0).
Since we are deriving ground terms, sort/2 removes duplicates.
Example query:
?- derivables(Ds).
ERROR: Arguments are not sufficiently instantiated
Somewhat anticlimactically, the abstract interpreter is unable to process this program!
Commutativity to the rescue
In a proof-theoretic approach, we search for, well, proofs. In an interpreter-based approach, we can either improve the interpreter or apply algebraic laws to transform the source program in a way that preserves essential properties.
In this case, I will do the latter, and leave the former as an exercise. Instead of searching for proofs, we are searching for equivalent ways to write the program so that our interpreter can derive the desired properties. For example, I now use commutativity of conjunction to obtain:
f(_, X, Y) :-
X = 1,
Y = s(1).
f(Arg, X, Y) :-
Arg = 1,
f(Arg, X0, Y0),
X = s(X0),
Y = s(s(Y0)).
f(X, Y, T) :-
f(X, Y0, Z),
f(X0, Z, T),
X = s(X0),
Y = s(Y0).
Again, I leave it as an exercise to carefully check that this program is declaratively equivalent to your original program.
iamque opus exegi, because:
?- derivables(Ds).
Ds = [f(any, d, d)].
This shows that in each solution of f/3, the last two arguments are always terms for which d/1 holds! In particular, it also holds for the sample arguments you posted, even if there is no hope to ever actually compute the concrete terms!
Conclusion
By abstract interpretation, we have shown:
for all X where f(_, _, X) holds, d(X) also holds
beyond that, for all Y where f(_, Y, _) holds, d(Y) also holds.
The question only asked for a special case of the first property. We have shown significantly more!
In summary:
If f(_, Y, X) holds, then d(X) holds and d(Y) holds.
Prolog makes it comparatively easy and convenient to reason about Prolog programs. This often allows us to derive interesting properties of Prolog programs, such as termination properties and type information.
Please see Reasoning about programs for references and more explanation.
+1 for a great question and reference.

Number of occurrences of X in the List L in prolog

I am trying to find the number of occurrences of X in the List L
For eg :-
occurrences(a, [b, a, b, c, a, d, a], N ).
N =3
My code not working .Here is my code.
occ(K,L,N) :- N1=0, occ1(K,L,N1,N).
occ1(K,[],N1,N) :- N=N1.
occ1(K,L,N1,N) :-
L=[X|L1],
( K=X -> N1 is N1+1, occ1(K,L1,N1,N) ; occ1(K,L1,N1,N) ).
Can anybody tell me what's wrong in the code.
While the answer given by #Kay is spot-on as far as fixing the bug is concerned, it completely circumvents a much bigger issue: The code of occ1/4 is logically impure.
This may not appear very important to you right now,
but using impure code has several negative consequences:
Impure code cannot be read declaratively, only procedurally.
Debugging impure code is often tedious and pain-staking.
Impure predicates are less "relational" than their pure counterparts.
Logical impurity hampers code re-use.
Because it is non-monotone, impure code is prone to lead to logically unsound answers, particularly when working with non-ground terms.
To show that these problems persisted in your code after having been "fixed" as suggested #Kay, let us consider the "corrected" code and some queries. First, here's the corrected code:
occ(K,L,N) :- N1=0, occ1(K,L,N1,N).
occ1(_,[],N1,N) :- N=N1.
occ1(K,L,N1,N) :-
L=[X|L1],
( K=X -> N2 is N1+1, occ1(K,L1,N2,N) ; occ1(K,L1,N1,N) ).
Here's the query you gave in your question:
?- occ(a,[b,a,b,c,a,d,a],N).
N = 3 ;
false.
Okay! What if we write the query differently?
?- A=a,B=b,C=c,D=d, occ(a,[B,A,B,C,A,D,A],N).
A = a, B = b, C = c, D = d, N = 3 ;
false.
Okay! What if we reorder goals? Logical conjunction should be commutative...
?- occ(a,[B,A,B,C,A,D,A],N), A=a,B=b,C=c,D=d.
false.
Fail! It seemed that occ1/4 is fine, but now we get an answer that is logically unsound.
This can be avoided by using logically pure code:
Look at the pure and monotone code I gave in my answer to the related question "Prolog - count repititions in list (sic)".
The problem is
N1 is N1+1
Variables cannot be "overwritten" in Prolog. You need to just a new variable, e.g.
N2 is N1+1, occ1(K,L1,N2,N)
To your question "Can we replace a particular list element. If yes, what is the syntax?":
You can only build a new list:
replace(_, _, [], []).
replace(Old, New, [H0|T0], [H1|T1]) :-
(H0 = Old -> H1 = New; H1 = H0),
replace(Old, New, T0, T1).

How to make a fact work both way in Prolog (takes arguments in any order)?

I want to define facts which are true both ways (They all have an arity of 2). I had success with a fact expressing the relationship "opposite" this way:
oppositeDeclare(plus, minus).
opposite(X, Y) :- oppositeDeclare(Y, X).
opposite(X, Y) :- oppositeDeclare(X, Y).
I'm trying to make a simple equation solver, and I would also like to define that if A=B then B=A. I can't just write:
equal(A, B):-equal(B,A).
because I get out of local stack error. However I can't do the same as I did with the "opposite" fact because "equal" needs to work based on the some rules. ("opposite" got it's input from facts only).
Is there a way I can avoid defining all the rules for "equal" twice?
Edit:
I only want to declare simple mathematical facts to see if I can use Prolog to solve other more complicated tasks where I don't know the mechanism for the solution only simple facts.
So far I have used equal/2 to define things like: if A=B+C, then C=A-B. I want to define equal/2 two ways so that I don't have to define if B+C=A, then A-B=C. Ideally after the new rule it could solve an equation for c like this: a=(b+c)/d -> b+c=a/d -> c=(a/d)-b.
The reason I can't use swap is because I have recursive rules for equal/2.
Bear in mind that it will not always work even for simple equations, because not all necessary facts are defined.
Here's the current program with a query:
5 ?- equal(v, X).
X = opr(s, per, t)
% operators: per, times, plus, minus
% equation(LHS, RHS): used to declare physics equations
% equal(LHS, RHS): checks equality in a query
equation(s, opr(v, times, t)). % s=v*t
equation(a, opr(opr(b, plus, c), per, d)). % a=(b+c)/d
oppositeDeclare(plus, minus).
oppositeDeclare(per, times).
opposite(X, Y) :- oppositeDeclare(Y, X).
opposite(X, Y) :- oppositeDeclare(X, Y).
equal(R, opr(A, O, B)) :- equation(R, opr(A, O, B)).
% if there's an equation R=A O B , then R = A O B, where O is an operator (+-*/)
equal(A, opr(R, OY, B)) :- equal(R, opr(A, OX, B)), opposite(OY, OX).
%declaring in one go: if R=A/B then A=R*B, if R=A-B then A=R+B, if R=A+B then A=R-B, if R=A-B then A=R+B
I am not sure I understand you correctly, but aren't you after this simple code?
equal(X, X).
Could you please show some sample input and output that you would like to achieve by using equal/2?
And about opposites, I would write this that way:
oppositeDeclare(plus, minus).
oppositeDeclare(good, evil).
oppositeDeclare(windows, linux).
swap(P, X, Y) :- permutation([X,Y], [X1,Y1]), call(P, X1, Y1).
opposite(X, Y) :- swap(oppositeDeclare, X, Y).
Anytime you would like to use predicate with arity 2 and try swapping arguments, you can use swap/3 in a way presented above.

prolog and translating propositional logic

My ultimate goal is to load a set of propositional formulas in to Prolog from a file in order to deduce some facts. Suppose I have the propositional formula:
p implies not(q).
In Prolog this would be:
not(q) :- p
Prolog does not seem to like the not operator in the head of the rule. I get the following error:
'$record_clause'/2: No permission to redefine built-in predicate `not/1'
Use :- redefine_system_predicate(+Head) if redefinition is intended
I know two ways to rewrite the general formula for p implies q. First, use the fact that the contrapositive is logically equivalent.
p implies q iff not(q) implies not(p)
Second, use the fact that p implies q is logically equivalent to not(p) or q (the truth tables are the same).
The first method leads me to my current problem. The second method is then just a conjunction or disjunction. You cannot write only conjunctions and disjunctions in Prolog as they are not facts or rules.
What is the best way around my problem so that I can express p implies not(q)?
Is it possible to write all propositional formulas in Prolog?
EDIT: Now I wish to connect my results with other propositional formulae. Suppose I have the following rule:
something :- formula(P, Q).
How does this connect? If I enter formula(false, true) (which evaluates to true) into the interpreter, this does not automatically make something true. Which is what I want.
p => ~q === ~p \/ ~q === ~( p /\ q )
So we can try to model this with a Prolog program,
formula(P,Q) :- P, Q, !, fail.
formula(_,_).
Or you can use the built-in \+ i.e. "not", to define it as formula(P,Q) :- \+( (P, Q) ).
This just checks the compliance of the passed values to the formula. If we combine this with domain generation first, we can "deduce" i.e. generate the compliant values:
13 ?- member(Q,[true, false]), formula(true, Q). %// true => ~Q, what is Q?
Q = false.
14 ?- member(Q,[true, false]), formula(false, Q). %// false => ~Q, what is Q?
Q = true ;
Q = false.
You are using the wrong tool. Try Answer Set Programming.

Resources