Search code examples
prologlogicswi-prologsat

Prolog output should match the input of the sat solver


Another user created a very useful code for me, which i will be using to create random k-cnf formulas. I found a good sat-solver online which i want to use to check the satisfiability of the k-cnf formula. There is a problem though. The output is different from the sat-solver its input, i would like this to match, in order for me to feed the random generated k-cnf formulas to the sat solver.

Code for the k-cnf generator

     random_knowledge_base(NumberOfClauses,
                      LiteralsPerClause,
                      AmountOfLiterals,
                      Ratio,
                      KnowledgeBase) :-
    length(KnowledgeBase, NumberOfClauses),
    maplist(random_clause(LiteralsPerClause, AmountOfLiterals, Ratio), KnowledgeBase).


random_clause(LiteralsPerClause, AmountOfLiterals, Ratio, Head :- Body) :-
    randseq(LiteralsPerClause, AmountOfLiterals, [Number|Numbers]), 
    atom_concat(p, Number, Head),  
    maplist(literal(Ratio), Numbers, Literals), 
    comma_list(Body, Literals). 


literal(Ratio, Number, Literal) :- 
    atom_concat(p, Number, Proposition)
    (   maybe(Ratio)  
    ->  Literal = Proposition 
    ;   Literal = -Proposition ). 

The predicate comma_list/2 can be used to transform a list of literals into a conjunction:

For example

?- randseq(3, 5, Ns), maplist(literal(0.5), Ns, Ls), comma_list(B, Ls).

Ns = [1, 5, 3],

Ls = [p1, p5, p3],

B =  (p1, p5, p3).

when querying

?- random_knowledge_base(4, 3, 5, 0.5, KB).

The output

KB = [(p1, -p4, -p5),  (p2, p3, p4),  (p2, p3, -p5),  (p3, -p1, -p4)].

But i want the output to stay int the "Ls" form, meaning that it would look like this

KB = [[p1, -p4, -p5],  [p2, p3, p4],  [p2, p3, -p5],  [p3, -p1, -p4]].

Because eventually, i want the output to look like this, to match this input of the sat solver:

?- sat([[true-X,false-Y],[false-X,false-Y],[true-X,true-Z]],[X,Y,Z]).

where The solver is called with two arguments. The first represents a formula in CNF as a list of lists, each constituent list representing a clause. The literals of a clause are represented as pairs, Pol-Var, where Var is a logical variable and Pol is true or false, indicating that the literal has positive or negative polarity. The formula ¬x∨(y ∧ ¬z) would thus be represented in CNF as (¬x∨y)∧(¬x∨ ¬z) and presented to the solver as the list

L = [[false-X, true-Y], [false-X,
false-Z]]

where X, Y and Z are logical variables. The second argument is a list of the variables occurring in the problem. Thus the query sat(L, [X, Y, Z]) will succeed and bind the variables to a solution, for example,

X = false, Y
= true, Z = true.

As a by-product, L will be instantiated to

[[false-false,
true-true], [false-false, false-true]].

This illustrates that the interpretation of true and false in L depends on whether they are left or right of the - operator: to the left they denote polarity; to the right, they denote truth values. If L is unsatisfiable then sat(L, Vars) will fail. If necessary, the solver can be called under a double negation to check for satisfiability, whilst leaving the variables unbound.

Code for checking satisfiability

sat(Clauses, Vars) :-
     problem_setup(Clauses), elim_var(Vars).

elim_var([]).
elim_var([Var | Vars]) :-
     elim_var(Vars), (Var = true; Var = false).

problem_setup([]).
problem_setup([Clause | Clauses]) :-
     clause_setup(Clause),
     problem_setup(Clauses).

clause_setup([Pol-Var | Pairs]) :- set_watch(Pairs, Var, Pol).

set_watch([], Var, Pol) :- Var = Pol.
set_watch([Pol2-Var2 | Pairs], Var1, Pol1):-
     watch(Var1, Pol1, Var2, Pol2, Pairs).

:- block watch(-, ?, -, ?, ?).
watch(Var1, Pol1, Var2, Pol2, Pairs) :-
     nonvar(Var1) ->
        update_watch(Var1, Pol1, Var2, Pol2, Pairs);
        update_watch(Var2, Pol2, Var1, Pol1, Pairs).

update_watch(Var1, Pol1, Var2, Pol2, Pairs) :-
        Var1 == Pol1 -> true; set_watch(Pairs, Var2, Pol2).

Anyone that could help would be my hero :) credits to @slago

ps if the output has to be restricted to a certain K, i.e. 3-cnf, to fit the sat solver, that would also be fine


Solution

  • The code must be altered as follows:

    random_knowledge_base(NumberOfClauses,
                          LiteralsPerClause,
                          AmountOfLiterals,
                          Ratio,
                          KnowledgeBase,
                          Variables ) :-     % <== Add output argument to get used variables
        length(KnowledgeBase, NumberOfClauses),
        length(Variables, AmountOfLiterals), % <== Generate a set of variables to be used
        maplist(random_clause(LiteralsPerClause, AmountOfLiterals, Variables, Ratio), KnowledgeBase).
    
    random_clause(LiteralsPerClause, AmountOfLiterals, Variables, Ratio, Literals) :-
        randseq(LiteralsPerClause, AmountOfLiterals, Numbers),
        maplist(literal(Ratio,Variables), Numbers, Literals).
    
    literal(Ratio, Variables, Number, Literal) :-
        nth1(Number, Variables, Proposition), % <== Map each number into a corresponding variable
        (   maybe(Ratio)
        ->  Literal = true-Proposition
        ;   Literal = false-Proposition ).
    

    Examples:

    ?- literal(0.5, [X,Y,Z], 1, Literal).
    Literal = false-X.
    
    ?- literal(0.5, [X,Y,Z], 2, Literal).
    Literal = true-Y.
    
    ?- random_clause(2, 3, [X,Y,Z], 0.5, C).
    C = [false-X, true-Y].
    
    ?- random_clause(2, 3, [X,Y,Z], 0.5, C).
    C = [true-Z, true-X].
    
    ?- random_knowledge_base(3, 2, 3, 0.5, K, V).
    K = [[false-_A, false-_B], [false-_C, true-_A], [false-_B, false-_C]],
    V = [_B, _A, _C].
    
    
    ?- random_knowledge_base(3, 2, 3, 0.5, K, V), sat(K, V).
    K = [[false-true, true-true], [true-true, true-true], [true-true, true-true]],
    V = [true, true, true] ;
    K = [[false-false, true-true], [true-false, true-true], [true-true, true-false]],
    V = [false, true, true] ;
    K = [[false-true, true-true], [true-true, true-true], [true-false, true-true]],
    V = [true, true, false] ;
    false.
    

    EDIT Use the following predicate to see the random knowledge base before the instatiations made by sat/2:

    show(K, V) :-
        copy_term(K-V, Kn-Vn),
        numbervars(Kn-Vn),
        format('Knowledge base: ~w\n', [Kn]),
        format('Propositions..: ~w\n\n', [Vn]).
    

    Example:

    ?- random_knowledge_base(3, 2, 3, 0.5, K, V), show(K, V), sat(K, V).
    Knowledge base: [[false-A,true-B],[true-C,true-A],[false-A,false-C]]
    Propositions..: [C,A,B]
    
    K = [[false-true, true-true], [true-false, true-true], [false-true, false-false]],
    V = [false, true, true] ;
    K = [[false-false, true-true], [true-true, true-false], [false-false, false-true]],
    V = [true, false, true] ;
    K = [[false-false, true-false], [true-true, true-false], [false-false, false-true]],
    V = [true, false, false] ;
    false.