1 module satd.tseytin;
2 import pegged.grammar;
3 import pegged.tohtml;
4 import satd.dimacs;
5 import satd.cnf;
6 import std.stdio;
7 import std.string : strip;
8 import std.container : redBlackTree;
9 import std.array : array;
10 import std.range : enumerate;
11 import std.typecons : Tuple;
12 
13 mixin(grammar(`
14 Expression:
15     Expr < Imp (OrOperator Imp)*
16     Imp < Conj (ImpOperator Conj / EquivOperator Conj)*
17     Conj < Unary (ConjOperator Unary)*
18     Unary < (UnaryOperator)? Primary
19     Primary < "(" Expr ")" / Ident
20 
21     OrOperator < "or" / "∨" / "v" / "\\/"
22     ImpOperator < "implies" / "then" / "→" / "⇒" / "->" / "=>"
23     EquivOperator < "equiv" / "⇔" / "↔" / "<=>"
24 
25     ConjOperator < "and" / "∧" / "^" / "/\\"
26     UnaryOperator < "not" / "-" / "¬" / "!"
27     Keyword < OrOperator / ImpOperator / EquivOperator / ConjOperator / UnaryOperator / "__VAR" ~[0-9]+
28     Ident <- !Keyword ~[0-9A-Za-z_]+
29 `));
30 
31 debug
32 {
33     import satd.solvers.cdcl;
34 
35     enum bool[string] answers = [
36             "not (a or not (b and c))" : true,
37             "not (a -> (b and c)) -> (b or not d equiv a)" : true,
38             "a and not a" : false, "not (a and not a)" : true
39         ];
40     bool isSAT(string formula)
41     {
42         CDCLSolver solver = new CDCLSolver();
43         solver.initialize(tseytinTransform(formula).parseResult);
44         return solver.solve().peek!(Literal[]) !is null;
45     }
46 
47     unittest
48     {
49         assert(isSAT(""));
50         assert(isSAT(" "));
51         assert(isSAT("a"));
52         assert(isSAT("A or B and A or not B or not A or not B"));
53         assert(isSAT("A or B and not A or B and A or not B and not A or not B"));
54         assert(isSAT("a or not a"));
55         assert(!isSAT("a and not a"));
56         assert(!isSAT("A /\\ !A"));
57 
58         // check whether all the symbols can be interpreted correctly...
59         assert(isSAT("A or A ∨ A v A \\/ A implies B then B → B ⇒ B -> B equiv C ⇔ C ↔ C <=> D and D ∧ D ^ D /\\ D or not E or -D or ¬ D or ! D"));
60         import std.exception : collectException;
61 
62         // check that exceptions should be thrown if the input is invalid
63         assert(collectException(isSAT("a or v")));
64         assert(collectException(isSAT("not")));
65         assert(isSAT("A -> A"));
66         assert(isSAT("A implies A"));
67         assert(isSAT("not (a or not (b and c))"));
68         assert(isSAT("not (a -> (b and c)) -> (b or not d equiv a)"));
69     }
70 
71     unittest
72     {
73         assert("(a)" !in tseytinTransform("(a)").varToLiteral);
74         assert("a" in tseytinTransform("(a)").varToLiteral);
75     }
76 }
77 
78 enum ExprType
79 {
80     NOT,
81     OR,
82     AND,
83     IMP,
84     EQUIV,
85 }
86 
87 struct Expr
88 {
89     string left;
90     ExprType type;
91     string right;
92 }
93 
94 ExprType toExprType(string name)
95 {
96     with (ExprType)
97     {
98         enum ExprType[string] exprMap = [
99                 "Expression.OrOperator" : OR, "Expression.ImpOperator" : IMP,
100                 "Expression.EquivOperator" : EQUIV,
101                 "Expression.ConjOperator" : AND, "Expression.UnaryOperator" : NOT
102             ];
103         return exprMap[name];
104     }
105 }
106 
107 alias tseytinTransformResult = Tuple!(parseResult, "parseResult", Literal[string], "varToLiteral");
108 
109 tseytinTransformResult tseytinTransform(string input)
110 {
111     input = input.strip;
112     // if the input is just blanks
113     if (input == "")
114         return tseytinTransformResult(parseResult([], Preamble(0, 0)), null);
115 
116     auto inputExpr = Expression(input);
117     if (inputExpr.end != input.length)
118         error("invalid input");
119 
120     Expr[] expressions;
121     Set!string originalVars = redBlackTree!string;
122     string getPreviousVariable()
123     {
124         assert(expressions.length != 0);
125         return format("__VAR%d", expressions.length - 1);
126     }
127 
128     PT visit(PT)(PT p)
129     {
130         switch (p.name)
131         {
132         case "Expression":
133             return visit(p.children[0]);
134         case "Expression.Expr", "Expression.Imp",
135                 "Expression.Conj":
136                 if (p.children.length == 1) return visit(p.children[0]);
137             Expr lastExpr = Expr(visit(p.children[0]).input,
138                     p.children[1].name.toExprType, visit(p.children[2]).input);
139             expressions ~= lastExpr;
140 
141             ulong k = 3;
142             while (k < p.children.length)
143             {
144                 ExprType type = p.children[k].name.toExprType;
145                 lastExpr = Expr(getPreviousVariable(), type, visit(p.children[k + 1]).input);
146                 expressions ~= lastExpr;
147                 k += 2;
148             }
149             p.input = getPreviousVariable();
150             return p;
151 
152         case "Expression.Unary":
153             if (p.children.length == 1)
154                 return visit(p.children[0]);
155 
156             auto res = visit(p.children[1]);
157             expressions ~= Expr(res.input, ExprType.NOT);
158             res.input = getPreviousVariable();
159             return res;
160 
161         case "Expression.Primary":
162             if (p.children.length == 1)
163                 return visit(p.children[0]);
164             else
165                 return visit(p.children[1]);
166 
167         case "Expression.Ident":
168             p.input = p.input[p.begin .. p.end].strip;
169             originalVars.insert(p.input);
170             return p;
171 
172         default:
173             error("invalid formula");
174             assert(0);
175         }
176 
177     }
178 
179     visit(inputExpr);
180 
181     // if the input is like "a", there's only one variable left and no operations included
182     if (expressions.length == 0)
183     {
184         return tseytinTransformResult(parseResult([Clause(1, [1])], Preamble(1,
185                 1)), [originalVars.front: 1]);
186     }
187 
188     auto vars = redBlackTree!string;
189     foreach (ind, expr; expressions)
190     {
191         vars.insert(expr.left);
192         vars.insert(expr.right);
193         vars.insert(format("__VAR%d", ind));
194     }
195     vars.removeKey("");
196     long[string] m;
197     foreach (i, v; vars.array.enumerate)
198         m[v] = i + 1;
199     m[""] = 0;
200 
201     size_t clauseVarNum = 1;
202     Clause newClause(T...)(T literals)
203     {
204         return Clause(clauseVarNum++, [literals]);
205     }
206 
207     Clause[] clauses;
208 
209     Clause[] exprToClauses(size_t ind, Expr expr)
210     {
211         auto tseytinLit = m[format("__VAR%d", ind)];
212         auto left = m[expr.left], right = m[expr.right];
213         with (ExprType) switch (expr.type)
214         {
215         case NOT:
216             return [
217                 newClause(tseytinLit, left), newClause(-tseytinLit, -left)
218             ];
219         case OR:
220             return [
221                 newClause(tseytinLit, -left), newClause(tseytinLit, -right),
222                 newClause(-tseytinLit, left, right)
223             ];
224         case AND:
225             return [
226                 newClause(-tseytinLit, left), newClause(-tseytinLit, right),
227                 newClause(tseytinLit, -left, -right)
228             ];
229         case IMP:
230             return [
231                 newClause(tseytinLit, left), newClause(tseytinLit, -right),
232                 newClause(-tseytinLit, -left, right)
233             ];
234         case EQUIV:
235             return [
236                 newClause(tseytinLit, left), newClause(tseytinLit, -right),
237                 newClause(-tseytinLit, -left, right), newClause(tseytinLit,
238                         right), newClause(tseytinLit, -left),
239                 newClause(-tseytinLit, -right, left)
240             ];
241         default:
242             assert(0);
243         }
244     }
245 
246     foreach (ind, expr; expressions)
247     {
248         clauses ~= exprToClauses(ind, expr);
249     }
250     clauses ~= newClause(m[format("__VAR%d", expressions.length - 1)]);
251 
252     Literal[string] varToLiteral;
253     foreach (var; originalVars)
254     {
255         varToLiteral[var] = m[var];
256     }
257 
258     return tseytinTransformResult(parseResult(clauses, Preamble(vars.length,
259             clauseVarNum - 1)), varToLiteral);
260 }
261 
262 bool[string] resultToOriginalVarsAssignment(tseytinTransformResult transformResult,
263         Literal[] solverResultLiterals)
264 {
265     import std.math : abs;
266 
267     if (solverResultLiterals is null)
268         return null;
269 
270     bool[string] assignment;
271     bool[Literal] litToTruth;
272     foreach (lit; solverResultLiterals)
273     {
274         litToTruth[abs(lit)] = lit > 0;
275     }
276     foreach (var, lit; transformResult.varToLiteral)
277     {
278         assignment[var] = litToTruth[lit];
279     }
280     return assignment;
281 }
282 
283 private:
284 import std.string : format;
285 
286 void error(A...)(string msg, A args)
287 {
288     class ExpressionReadException : Exception
289     {
290         this(string msg)
291         {
292             super(msg);
293         }
294     }
295 
296     throw new ExpressionReadException(format(msg, args));
297 }