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 }