Use prolog to show cause of boolean logic failure

旧时模样 提交于 2019-12-08 03:31:08

问题


Suppose i have the following boolean logic:

Z = (A or B) and (A or C)

Is it possible to use prolog possibly (maybe together with some library) to figure out why Z is false and to return the answer in the following formats:

  1. Z is false because A or (b and c) are false
  2. If i substitute some known values (or all) like (c = true) that it will say: Z is false because A is false
  3. It can tell me which rule or which part of the rule causes this problem: Z is false because A is false in (A or B) of "Z = (A or B) and (A or C)?

Same questions again for when Z = true with everything in reverse.

Or are these kind of questions not suitable for prolog and i should look at some SAT solver or something else?

My goal is to analyze dataflow of a program and figure answer questions like. I expect this to be true/false but it's not, what could it be?


回答1:


I have to say this is kind of a cool problem.

I see two fundamental approaches, one in which you use Prolog's read_term/2 to obtain a term along with the variable names used in the term, which would look something like this:

?- read_term(T, [variable_names(Names)]).
|: X and Y or Z and Q.

T = _5700 and _5702 or _5706 and _5708,
Names = ['X'=_5700, 'Y'=_5702, 'Z'=_5706, 'Q'=_5708].

Giving it some thought, it seemed like this would be more trouble than it would be worth for me, so I thought I'll illustrate a "simpler" version where we don't really have variables, just atoms and a separate list of variable values. You probably could adapt the one below to the one above without a huge amount of work though, it just didn't seem really necessary to solving the problem.

First we need to support these operators:

:- op(500, yfx, or).
:- op(400, yfx, and).

I went ahead and gave these the same precedence as + and * respectively, that just seemed intuitive to me.

I'm going to have a list of variables and assignments, like [x=true, y=false]. I created a predicate to manage that, but I thought the better of it eventually and removed it, but let's note that we made this decision here.

Now my plan is to create an evaluation predicate that is going to take an expression and the variable-value assignments. It's going to produce a boolean for the supplied expression and a "reason". I'm going to cheat here and use the "is" operator, but I'm only doing this because it reads nicely to me. The most basic kind of term is

evaluate(X, Assignments, Value, X is Value) :-
    atom(X), memberchk(X=Value, Assignments).

So we can now support expressions like x:

?- evaluate(x, [x=true], V, R).
V = true,
R =  (x is true).

?- evaluate(x, [x=false], V, R).
V = false,
R =  (x is false).

These are sort of like tautologies; if x=true, then the expression "x" is true because x=true. Likewise for false. But we have the reason there, which is what you're after. So let's handle "or" next:

evaluate(X or _, Assignments, true, Reason) :-
    evaluate(X,  Assignments, true, Reason).
evaluate(_ or Y, Assignments, true, Reason) :-
    evaluate(Y,  Assignments, true, Reason).

So now we should handle the case where one of the two is true:

?- evaluate(x or y, [x=true,y=false], V, R).
V = true,
R =  (x is true) ;
false.

?- evaluate(x or y, [x=false,y=true], V, R).
V = true,
R =  (y is true) ;
false.

If we give it x=true and y=true, we will get two solutions, one for x and one for y. But we need one more rule for the false case:

evaluate(X or Y, Assignments, false, R1 and R2) :-
    evaluate(X,  Assignments, false, R1),
    evaluate(Y,  Assignments, false, R2).

So the idea here is to notice that both sides of the "or" are false and then combine the reasons with "and". This way we get this:

?- evaluate(x or y, [x=false,y=false], V, R).
V = false,
R =  (x is false)and(y is false).

Because we delegate the sides, we can now see that large and complex "or" sequences should work:

?- evaluate(a or b or c or d or e, [a=false,b=false,c=false,d=true,e=false], V, R).
V = true,
R =  (d is true) ;
false.

You can extrapolate what we did for "or" for "and", where we basically have two false cases and one true case instead of two true cases and one false case:

evaluate(X and Y, Assignments, true, R1 and R2) :-
    evaluate(X,   Assignments, true, R1),
    evaluate(Y,   Assignments, true, R2).
evaluate(X and _, Assignments, false, Reason) :-
    evaluate(X,   Assignments, false, Reason).
evaluate(_ and Y, Assignments, false, Reason) :-
    evaluate(Y,   Assignments, false, Reason).

This works for some interesting cases you might expect:

?- evaluate(x and y or z, [x=true, y=true, z=false], V, R).
V = true,
R =  (x is true)and(y is true) ;
false.

?- evaluate(x and y or z, [x=false, y=false, z=true], V, R).
V = true,
R =  (z is true) ;
false.

There are some situations where this isn't super useful, such as this one:

?- evaluate((a or b) and (a or c), [a=true,b=false,c=false], V, R).
V = true,
R =  (a is true)and(a is true) ;
false.

As you can see the first solution is not super informative because we have already said something about a; maybe we could find a way to simplify the answer by combining the answers from the children more intelligently. On the other hand, it is somewhat more helpful in this case:

?- evaluate((a or b) and (a or c), [a=false,b=false,c=false], V, R).
V = false,
R =  (a is false)and(b is false) ;
V = false,
R =  (a is false)and(c is false).

?- evaluate((a or b) and (a or c), [a=false,b=true,c=true], V, R).
V = true,
R =  (b is true)and(c is true) ;
false.

Edit: Handling undefined values

The only piece that really needs to be changed to handle undefined values is the atom(X) branch, which should be replaced with this:

evaluate(X, Assignments, Value, X is Value) :-
    atom(X),
    (    memberchk(X=V, Assignments) ->
         Value = V
    ;    member(Value, [true,false])
    ).

When a=false appears in the binding list, it will be used; if it doesn't appear, then both a=false and a=true will be generated. This appears to cover your other use cases, starting with the completely general:

?- evaluate((a or b) and (a or c), [a=false], V, Reason).
V = true,
Reason =  (b is true)and(c is true) ;

V = false,
Reason =  (a is false)and(b is false) ;

V = false,
Reason =  (a is false)and(c is false).

And you can restrict the search to cases that produce the value you're interested in:

?- evaluate((a or b) and (a or c), [a=false], false, Reason).
Reason =  (a is false)and(b is false) ;
Reason =  (a is false)and(c is false).

Of course, Prolog isn't doing anything especially smart here; it isn't trying to work backwards to figure out what values of b and c are going to lead to false, it's just generating all the possibilities and trying to unify their evaluation with false. So each undefined variable you add to the expression is going to double the search space. If this is the inefficiency you're worried about, this may not be an ideal solution for you (or, it might be fine, you might try it and see if it's tolerable).

I think you might want to look into SAT solvers if your primary concern is performance, although I don't know offhand if they are capable of giving you "reasons" back for their inferences.



来源:https://stackoverflow.com/questions/55129499/use-prolog-to-show-cause-of-boolean-logic-failure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!